jzou19950715 commited on
Commit
f6b0589
·
verified ·
1 Parent(s): e576ca7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -114
app.py CHANGED
@@ -11,150 +11,226 @@ import io
11
  logging.basicConfig(level=logging.INFO)
12
  logger = logging.getLogger(__name__)
13
 
14
- # Prompts remain the same
15
- CONVERSATION_PROMPT = """You are LOSS DOG, a professional profile builder. Your goal is to have natural conversations
16
- with users to gather information about their professional background across 9 categories:
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  1. Work History & Experience
 
 
 
 
19
  2. Salary & Compensation
 
 
 
 
 
 
20
  3. Skills & Certifications
 
 
 
 
 
 
21
  4. Education & Learning
22
- 5. Personal Branding & Online Presence
23
- 6. Achievements & Awards
24
- 7. Social Proof & Networking
25
- 8. Project Contributions & Leadership
26
- 9. Work Performance & Impact Metrics
27
-
28
- Be friendly and conversational. Ask follow-up questions naturally. When appropriate, guide users to share more details
29
- but respect their boundaries. Once you believe you have gathered sufficient information (or if the user indicates they
30
- have nothing more to share), let them know they can click 'Generate Profile' to proceed.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  """
32
 
33
- EXTRACTION_PROMPT = """You are a professional information extraction system. Your task is to extract information from the potentially unstructure conversation and return ONLY a valid JSON object.
34
- Proactively determine how to fill the json schema using limited information provided.
 
35
  Do not include any explanatory text before or after the JSON.
36
  Return the data in this exact structure:
 
37
  {
38
  "work_history_experience": {
39
  "positions": [
40
  {
41
  "title": string,
42
  "company": string,
43
- "industry": string,
44
- "location": string,
45
- "employment_type": string,
46
- "adaptability": {
47
- "career_shifts": [],
48
- "upskilling": []
49
- },
50
- "promotions": [],
51
- "confidence": number
 
52
  }
53
  ]
54
  },
55
  "salary_compensation": {
56
  "history": [
57
  {
 
58
  "base_salary": number,
59
  "bonus_structure": string,
60
- "stock_options": {
61
  "type": string,
62
  "details": string
63
  },
64
- "commission": null,
65
  "benefits": {
66
  "health": string,
67
  "pto": string,
68
- "retirement": string,
69
  "other": []
70
  },
71
- "confidence": number
 
72
  }
73
  ]
74
  },
75
  "skills_certifications": {
76
- "hard_skills": [],
77
- "soft_skills": [],
78
- "certifications": [],
79
- "licenses": []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  },
81
  "education_learning": {
82
- "formal_education": [],
83
- "online_courses": [],
84
- "executive_education": []
85
- },
86
- "personal_branding": {
87
- "portfolio": {
88
- "github": null,
89
- "behance": null,
90
- "other": []
91
- },
92
- "blog_posts": [],
93
- "blockchain_projects": {
94
- "nfts": [],
95
- "defi": [],
96
- "dapps": []
97
  },
98
- "social_media": {
99
- "platforms": [],
100
- "influence_metrics": {}
101
- }
102
  },
103
  "achievements_awards": {
104
- "industry_awards": [],
105
- "hackathons": [],
106
- "peer_endorsements": [],
107
- "creative_projects": {
108
- "ai_art": [],
109
- "other": []
110
- }
 
 
 
 
 
 
 
 
 
 
 
111
  },
112
  "social_proof_networking": {
113
- "mentors": [],
114
- "references": [],
115
- "memberships": [],
116
- "conference_engagement": []
117
- },
118
- "project_contributions": {
119
- "major_projects": [],
120
- "open_source": [],
121
- "team_leadership": [],
122
- "patents": [],
123
- "impact": {
124
- "description": string,
125
- "metrics": [],
126
- "confidence": number
127
  }
128
- },
129
- "work_performance_metrics": {
130
- "kpis": [],
131
- "revenue_impact": [],
132
- "efficiency_gains": [],
133
- "career_growth": [],
134
- "leadership_influence": []
135
  }
136
  }
137
 
138
  IMPORTANT: Return ONLY the JSON. Do not add any explanation text."""
139
 
 
140
  class ProfileBuilder:
141
  def __init__(self):
142
  self.client = None
143
  self.pdf_text = None
144
 
145
  def _initialize_client(self, api_key: str) -> None:
146
- """Initialize OpenAI client if not already initialized"""
147
  if not api_key.startswith("sk-"):
148
  raise ValueError("Invalid API key format")
149
  self.client = AsyncOpenAI(api_key=api_key)
150
 
151
  async def process_message(self, message: str, history: List[List[str]], api_key: str) -> Dict[str, Any]:
152
- """Process a chat message using conversation history from Gradio's state"""
153
  try:
154
- # Initialize client if needed
155
  self._initialize_client(api_key)
156
 
157
- # Convert Gradio history format to OpenAI message format
158
  conversation_history = []
159
  for human, assistant in history:
160
  conversation_history.extend([
@@ -162,10 +238,8 @@ class ProfileBuilder:
162
  {"role": "assistant", "content": assistant}
163
  ])
164
 
165
- # Add current message
166
  conversation_history.append({"role": "user", "content": message})
167
 
168
- # Get AI response
169
  completion = await self.client.chat.completions.create(
170
  model="gpt-4o-mini",
171
  messages=[
@@ -175,7 +249,6 @@ class ProfileBuilder:
175
  temperature=0.7
176
  )
177
 
178
- # Extract response
179
  ai_message = completion.choices[0].message.content
180
  return {"response": ai_message}
181
 
@@ -184,7 +257,6 @@ class ProfileBuilder:
184
  return {"error": str(e)}
185
 
186
  async def extract_from_pdf(self, pdf_content: bytes) -> str:
187
- """Extract text from PDF file"""
188
  try:
189
  pdf_file = io.BytesIO(pdf_content)
190
  pdf_reader = PyPDF2.PdfReader(pdf_file)
@@ -198,15 +270,12 @@ class ProfileBuilder:
198
  raise
199
 
200
  async def process_pdf(self, pdf_path: str, api_key: str) -> Dict[str, Any]:
201
- """Process PDF resume"""
202
  try:
203
  self._initialize_client(api_key)
204
 
205
- # Read and extract PDF content
206
  with open(pdf_path, 'rb') as file:
207
  resume_text = await self.extract_from_pdf(file.read())
208
 
209
- # Process with AI
210
  completion = await self.client.chat.completions.create(
211
  model="gpt-4o-mini",
212
  messages=[
@@ -216,11 +285,9 @@ class ProfileBuilder:
216
  temperature=0.3
217
  )
218
 
219
- # Parse response
220
  response_text = completion.choices[0].message.content.strip()
221
  profile_data = json.loads(response_text)
222
 
223
- # Create profile object
224
  profile = {
225
  "profile_data": profile_data,
226
  "metadata": {
@@ -236,11 +303,9 @@ class ProfileBuilder:
236
  return {"error": str(e)}
237
 
238
  async def generate_profile(self, history: List[List[str]], api_key: str) -> tuple[Dict[str, Any], Optional[str]]:
239
- """Generate profile from conversation or PDF"""
240
  try:
241
  self._initialize_client(api_key)
242
 
243
- # Determine source and prepare content
244
  if history:
245
  content = "\n".join(f"User: {msg[0]}\nAssistant: {msg[1]}" for msg in history)
246
  source = "conversation"
@@ -250,7 +315,6 @@ class ProfileBuilder:
250
  else:
251
  raise ValueError("No content available for profile generation")
252
 
253
- # Get AI extraction
254
  completion = await self.client.chat.completions.create(
255
  model="gpt-4o-mini",
256
  messages=[
@@ -260,11 +324,9 @@ class ProfileBuilder:
260
  temperature=0.3
261
  )
262
 
263
- # Parse response
264
  response_text = completion.choices[0].message.content.strip()
265
  profile_data = json.loads(response_text)
266
 
267
- # Create profile
268
  profile = {
269
  "profile_data": profile_data,
270
  "metadata": {
@@ -273,7 +335,6 @@ class ProfileBuilder:
273
  }
274
  }
275
 
276
- # Save to file
277
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
278
  filename = f"profile_{timestamp}.json"
279
  with open(filename, 'w', encoding='utf-8') as f:
@@ -291,20 +352,17 @@ def create_gradio_interface():
291
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
292
  gr.Markdown("# 🐕 LOSS DOG - Professional Profile Builder")
293
 
294
- # Common API key input
295
  api_key = gr.Textbox(
296
  label="OpenAI API Key",
297
  type="password",
298
  placeholder="Enter your OpenAI API key"
299
  )
300
 
301
- # Tab interface
302
  with gr.Tabs() as tabs:
303
- # Resume Upload Tab
304
  with gr.Tab("Upload Resume"):
305
  gr.Markdown("""
306
  # Upload Your Resume
307
- Upload your existing resume in PDF format and let LOSS DOG extract your professional profile.
308
  """)
309
  pdf_file = gr.File(
310
  label="Upload PDF Resume",
@@ -312,11 +370,10 @@ def create_gradio_interface():
312
  )
313
  process_pdf_btn = gr.Button("Process Resume")
314
 
315
- # Chat Tab
316
- with gr.Tab("Chat with AI"):
317
  gr.Markdown("""
318
- # Chat with LOSS DOG
319
- Start a conversation with LOSS DOG to build your professional profile from scratch.
320
  """)
321
  chatbot = gr.Chatbot(
322
  label="Conversation",
@@ -325,21 +382,19 @@ def create_gradio_interface():
325
  with gr.Row():
326
  msg = gr.Textbox(
327
  label="Message",
328
- placeholder="Chat with LOSS DOG...",
329
  show_label=False
330
  )
331
  send = gr.Button("Send")
332
 
333
- # Common output section
334
  with gr.Column():
335
  generate_btn = gr.Button("Generate Profile", variant="primary")
336
  profile_output = gr.JSON(label="Generated Profile")
337
  download_btn = gr.File(label="Download Profile")
338
 
339
- # Event handlers
340
  async def on_message(message: str, history: List[List[str]], key: str):
341
  if not message.strip():
342
- return history, None, None, "" # Added empty string to clear input
343
 
344
  result = await builder.process_message(message, history, key)
345
 
@@ -347,7 +402,7 @@ def create_gradio_interface():
347
  return history, {"error": result["error"]}, None, message
348
 
349
  new_history = history + [[message, result["response"]]]
350
- return new_history, None, None, "" # Empty string to clear input
351
 
352
  async def on_pdf_upload(pdf, key):
353
  if not pdf:
@@ -358,7 +413,6 @@ def create_gradio_interface():
358
  if "error" in result:
359
  return {"error": result["error"]}, None
360
 
361
- # Save profile
362
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
363
  filename = f"profile_{timestamp}.json"
364
  with open(filename, 'w', encoding='utf-8') as f:
@@ -374,17 +428,16 @@ def create_gradio_interface():
374
  return {"error": profile["error"]}, None
375
  return profile["profile_data"], filename
376
 
377
- # Bind events
378
  msg.submit(
379
  on_message,
380
  inputs=[msg, chatbot, api_key],
381
- outputs=[chatbot, profile_output, download_btn, msg] # Added msg to outputs
382
  )
383
 
384
  send.click(
385
  on_message,
386
  inputs=[msg, chatbot, api_key],
387
- outputs=[chatbot, profile_output, download_btn, msg] # Added msg to outputs
388
  )
389
 
390
  process_pdf_btn.click(
 
11
  logging.basicConfig(level=logging.INFO)
12
  logger = logging.getLogger(__name__)
13
 
14
+ # Updated FINN Conversation Prompt
15
+ CONVERSATION_PROMPT = """
16
+ You are FINN (Facts, Insights, Numbers, and Narrative), Lossdog's dedicated AI assistant for portfolio and resume building.
17
+
18
+ Your Core Purpose:
19
+ Guide users in discovering and showcasing their professional worth through natural, engaging conversations.
20
+
21
+ Conversation Principles:
22
+ - Start with open-ended questions about their professional journey
23
+ - Listen actively and ask relevant follow-up questions
24
+ - Show genuine interest in their achievements and experiences
25
+ - Use casual, friendly language while maintaining professionalism
26
+ - Acknowledge and validate their experiences
27
+
28
+ You must gather detailed information across these 6 core categories:
29
 
30
  1. Work History & Experience
31
+ - Job titles, companies, tenure, responsibilities
32
+ - Team size, budget management, project scope
33
+ - Revenue impact, growth numbers, KPIs
34
+
35
  2. Salary & Compensation
36
+ - Current and past compensation packages
37
+ - Bonus structures and equity
38
+ - Benefits and additional perks
39
+ - Market rate comparisons
40
+ - Salary growth trajectory
41
+
42
  3. Skills & Certifications
43
+ - Technical and soft skills with proficiency levels
44
+ - Professional certifications with dates
45
+ - Tools and technologies mastered
46
+ - Languages and frameworks
47
+ - Industry-specific expertise
48
+
49
  4. Education & Learning
50
+ - Formal degrees and institutions
51
+ - Continuing education programs
52
+ - Professional development courses
53
+ - Self-taught skills and projects
54
+ - Learning goals and progress
55
+
56
+ 5. Achievements & Awards
57
+ - Professional recognition
58
+ - Project successes with metrics
59
+ - Patents and publications
60
+ - Innovation contributions
61
+ - Performance awards
62
+
63
+ 6. Social Proof & Networking
64
+ - Professional network size and quality
65
+ - Speaking engagements
66
+ - Published content and thought leadership
67
+ - Community involvement
68
+ - Industry influence metrics
69
+
70
+ Conversation Techniques:
71
+ DO use prompts like:
72
+ - "Could you tell me more about your role at [Company]?"
73
+ - "What specific metrics showcase your impact in that position?"
74
+ - "How would you quantify the results of that project?"
75
+ - "Can you share an example of how you applied [Skill]?"
76
+ - "What kind of recognition did you receive for that achievement?"
77
+
78
+ DON'T:
79
+ - Ask multiple questions at once
80
+ - Skip categories without proper exploration
81
+ - Accept vague answers without gentle follow-up
82
+ - Rush through topics
83
+ - Ignore potential achievements
84
+
85
+ Information Gathering Strategy:
86
+ 1. Start broad: "Tell me about your professional journey."
87
+ 2. Listen for category mentions
88
+ 3. Dive deeper with specific follow-ups
89
+ 4. Encourage quantitative metrics where possible
90
+ 5. Circle back to missing information naturally
91
+ 6. Confirm and validate gathered data
92
+
93
+ Make this feel like a natural conversation with a knowledgeable friend who's genuinely interested in their professional story, while systematically gathering comprehensive information across all six categories.
94
  """
95
 
96
+ # Updated Extraction Prompt to match FINN's categories
97
+ EXTRACTION_PROMPT = """You are a professional information extraction system. Extract information from the conversation and return ONLY a valid JSON object that matches FINN's 6 core categories.
98
+ Proactively determine how to fill the json schema using provided information.
99
  Do not include any explanatory text before or after the JSON.
100
  Return the data in this exact structure:
101
+
102
  {
103
  "work_history_experience": {
104
  "positions": [
105
  {
106
  "title": string,
107
  "company": string,
108
+ "tenure": string,
109
+ "responsibilities": [],
110
+ "team_size": number,
111
+ "budget_managed": string,
112
+ "project_scope": string,
113
+ "metrics": {
114
+ "revenue_impact": string,
115
+ "growth_numbers": string,
116
+ "kpis": []
117
+ }
118
  }
119
  ]
120
  },
121
  "salary_compensation": {
122
  "history": [
123
  {
124
+ "period": string,
125
  "base_salary": number,
126
  "bonus_structure": string,
127
+ "equity": {
128
  "type": string,
129
  "details": string
130
  },
 
131
  "benefits": {
132
  "health": string,
133
  "pto": string,
 
134
  "other": []
135
  },
136
+ "market_comparison": string,
137
+ "growth_trajectory": string
138
  }
139
  ]
140
  },
141
  "skills_certifications": {
142
+ "technical_skills": [
143
+ {
144
+ "name": string,
145
+ "proficiency": string
146
+ }
147
+ ],
148
+ "soft_skills": [
149
+ {
150
+ "name": string,
151
+ "proficiency": string
152
+ }
153
+ ],
154
+ "certifications": [
155
+ {
156
+ "name": string,
157
+ "date": string,
158
+ "issuer": string
159
+ }
160
+ ],
161
+ "tools_technologies": [],
162
+ "industry_expertise": []
163
  },
164
  "education_learning": {
165
+ "formal_education": [
166
+ {
167
+ "degree": string,
168
+ "institution": string,
169
+ "year": string,
170
+ "field": string
171
+ }
172
+ ],
173
+ "continuing_education": [],
174
+ "professional_development": [],
175
+ "self_learning": {
176
+ "skills": [],
177
+ "projects": []
 
 
178
  },
179
+ "learning_goals": []
 
 
 
180
  },
181
  "achievements_awards": {
182
+ "recognition": [
183
+ {
184
+ "title": string,
185
+ "issuer": string,
186
+ "date": string,
187
+ "description": string
188
+ }
189
+ ],
190
+ "project_successes": [
191
+ {
192
+ "name": string,
193
+ "metrics": [],
194
+ "impact": string
195
+ }
196
+ ],
197
+ "patents_publications": [],
198
+ "innovations": [],
199
+ "performance_awards": []
200
  },
201
  "social_proof_networking": {
202
+ "professional_network": {
203
+ "size": number,
204
+ "quality_metrics": string
205
+ },
206
+ "speaking_engagements": [],
207
+ "published_content": [],
208
+ "community_involvement": [],
209
+ "influence_metrics": {
210
+ "followers": number,
211
+ "engagement_rate": string,
212
+ "platform_presence": []
 
 
 
213
  }
 
 
 
 
 
 
 
214
  }
215
  }
216
 
217
  IMPORTANT: Return ONLY the JSON. Do not add any explanation text."""
218
 
219
+ # Rest of the code remains identical to the original implementation
220
  class ProfileBuilder:
221
  def __init__(self):
222
  self.client = None
223
  self.pdf_text = None
224
 
225
  def _initialize_client(self, api_key: str) -> None:
 
226
  if not api_key.startswith("sk-"):
227
  raise ValueError("Invalid API key format")
228
  self.client = AsyncOpenAI(api_key=api_key)
229
 
230
  async def process_message(self, message: str, history: List[List[str]], api_key: str) -> Dict[str, Any]:
 
231
  try:
 
232
  self._initialize_client(api_key)
233
 
 
234
  conversation_history = []
235
  for human, assistant in history:
236
  conversation_history.extend([
 
238
  {"role": "assistant", "content": assistant}
239
  ])
240
 
 
241
  conversation_history.append({"role": "user", "content": message})
242
 
 
243
  completion = await self.client.chat.completions.create(
244
  model="gpt-4o-mini",
245
  messages=[
 
249
  temperature=0.7
250
  )
251
 
 
252
  ai_message = completion.choices[0].message.content
253
  return {"response": ai_message}
254
 
 
257
  return {"error": str(e)}
258
 
259
  async def extract_from_pdf(self, pdf_content: bytes) -> str:
 
260
  try:
261
  pdf_file = io.BytesIO(pdf_content)
262
  pdf_reader = PyPDF2.PdfReader(pdf_file)
 
270
  raise
271
 
272
  async def process_pdf(self, pdf_path: str, api_key: str) -> Dict[str, Any]:
 
273
  try:
274
  self._initialize_client(api_key)
275
 
 
276
  with open(pdf_path, 'rb') as file:
277
  resume_text = await self.extract_from_pdf(file.read())
278
 
 
279
  completion = await self.client.chat.completions.create(
280
  model="gpt-4o-mini",
281
  messages=[
 
285
  temperature=0.3
286
  )
287
 
 
288
  response_text = completion.choices[0].message.content.strip()
289
  profile_data = json.loads(response_text)
290
 
 
291
  profile = {
292
  "profile_data": profile_data,
293
  "metadata": {
 
303
  return {"error": str(e)}
304
 
305
  async def generate_profile(self, history: List[List[str]], api_key: str) -> tuple[Dict[str, Any], Optional[str]]:
 
306
  try:
307
  self._initialize_client(api_key)
308
 
 
309
  if history:
310
  content = "\n".join(f"User: {msg[0]}\nAssistant: {msg[1]}" for msg in history)
311
  source = "conversation"
 
315
  else:
316
  raise ValueError("No content available for profile generation")
317
 
 
318
  completion = await self.client.chat.completions.create(
319
  model="gpt-4o-mini",
320
  messages=[
 
324
  temperature=0.3
325
  )
326
 
 
327
  response_text = completion.choices[0].message.content.strip()
328
  profile_data = json.loads(response_text)
329
 
 
330
  profile = {
331
  "profile_data": profile_data,
332
  "metadata": {
 
335
  }
336
  }
337
 
 
338
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
339
  filename = f"profile_{timestamp}.json"
340
  with open(filename, 'w', encoding='utf-8') as f:
 
352
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
353
  gr.Markdown("# 🐕 LOSS DOG - Professional Profile Builder")
354
 
 
355
  api_key = gr.Textbox(
356
  label="OpenAI API Key",
357
  type="password",
358
  placeholder="Enter your OpenAI API key"
359
  )
360
 
 
361
  with gr.Tabs() as tabs:
 
362
  with gr.Tab("Upload Resume"):
363
  gr.Markdown("""
364
  # Upload Your Resume
365
+ Upload your existing resume in PDF format and let FINN extract your professional profile.
366
  """)
367
  pdf_file = gr.File(
368
  label="Upload PDF Resume",
 
370
  )
371
  process_pdf_btn = gr.Button("Process Resume")
372
 
373
+ with gr.Tab("Chat with FINN"):
 
374
  gr.Markdown("""
375
+ # Chat with FINN
376
+ Start a conversation with FINN to build your professional profile from scratch.
377
  """)
378
  chatbot = gr.Chatbot(
379
  label="Conversation",
 
382
  with gr.Row():
383
  msg = gr.Textbox(
384
  label="Message",
385
+ placeholder="Chat with FINN...",
386
  show_label=False
387
  )
388
  send = gr.Button("Send")
389
 
 
390
  with gr.Column():
391
  generate_btn = gr.Button("Generate Profile", variant="primary")
392
  profile_output = gr.JSON(label="Generated Profile")
393
  download_btn = gr.File(label="Download Profile")
394
 
 
395
  async def on_message(message: str, history: List[List[str]], key: str):
396
  if not message.strip():
397
+ return history, None, None, ""
398
 
399
  result = await builder.process_message(message, history, key)
400
 
 
402
  return history, {"error": result["error"]}, None, message
403
 
404
  new_history = history + [[message, result["response"]]]
405
+ return new_history, None, None, ""
406
 
407
  async def on_pdf_upload(pdf, key):
408
  if not pdf:
 
413
  if "error" in result:
414
  return {"error": result["error"]}, None
415
 
 
416
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
417
  filename = f"profile_{timestamp}.json"
418
  with open(filename, 'w', encoding='utf-8') as f:
 
428
  return {"error": profile["error"]}, None
429
  return profile["profile_data"], filename
430
 
 
431
  msg.submit(
432
  on_message,
433
  inputs=[msg, chatbot, api_key],
434
+ outputs=[chatbot, profile_output, download_btn, msg]
435
  )
436
 
437
  send.click(
438
  on_message,
439
  inputs=[msg, chatbot, api_key],
440
+ outputs=[chatbot, profile_output, download_btn, msg]
441
  )
442
 
443
  process_pdf_btn.click(