Builder-Neekhil commited on
Commit
ecbcbc1
Β·
verified Β·
1 Parent(s): 93a77a7

Upload career_os_orchestrator.py

Browse files
Files changed (1) hide show
  1. career_os_orchestrator.py +523 -0
career_os_orchestrator.py ADDED
@@ -0,0 +1,523 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ═══════════════════════════════════════════════════════════════════════════════
3
+ CAREER OS β€” MULTI-AGENT ORCHESTRATOR
4
+ ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ Hierarchical multi-agent architecture inspired by AgentOrchestra (arXiv:2506.12508).
7
+ Each agent is a fine-tuned model specialized for one career domain.
8
+ The Orchestrator Agent delegates tasks and synthesizes outputs.
9
+
10
+ Agents:
11
+ 1. Resume Parser β€” PDF/text β†’ structured JSON
12
+ 2. Job Matcher β€” resume ↔ job description matching & scoring
13
+ 3. Career Advisor β€” path planning, skill gap analysis, interview prep
14
+ 4. Salary Negotiator β€” compensation strategies, market research
15
+ 5. Orchestrator β€” routes requests, manages context, synthesizes responses
16
+
17
+ Usage (Colab A100):
18
+ !pip install -q transformers peft accelerate
19
+ from career_os_orchestrator import CareerOS
20
+ cos = CareerOS(agent_model="Builder-Neekhil/career-agent-v1")
21
+ result = cos.process("Review my resume for a Senior PM role", resume_text)
22
+ print(result)
23
+
24
+ Or with individual agents:
25
+ cos.agents["resume_parser"].parse(pdf_path)
26
+ cos.agents["job_matcher"].score(resume, job_description)
27
+ ═══════════════════════════════════════════════════════════════════════════════
28
+ """
29
+
30
+ import json, re, os
31
+ from typing import Dict, List, Optional, Any
32
+ from dataclasses import dataclass
33
+
34
+ import torch
35
+ from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
36
+ from peft import PeftModel
37
+
38
+
39
+ @dataclass
40
+ class ParsedResume:
41
+ """Structured resume output."""
42
+ name: str = ""
43
+ email: str = ""
44
+ phone: str = ""
45
+ summary: str = ""
46
+ experience: List[Dict] = None
47
+ education: List[Dict] = None
48
+ skills: List[str] = None
49
+ certifications: List[str] = None
50
+ raw: str = ""
51
+
52
+ def __post_init__(self):
53
+ if self.experience is None:
54
+ self.experience = []
55
+ if self.education is None:
56
+ self.education = []
57
+ if self.skills is None:
58
+ self.skills = []
59
+ if self.certifications is None:
60
+ self.certifications = []
61
+
62
+
63
+ @dataclass
64
+ class JobFitResult:
65
+ """Job fit assessment result."""
66
+ fit_assessment: str = ""
67
+ score: int = 0
68
+ strengths: List[str] = None
69
+ gaps: List[str] = None
70
+ suggestions: List[str] = None
71
+ raw: str = ""
72
+
73
+ def __post_init__(self):
74
+ if self.strengths is None:
75
+ self.strengths = []
76
+ if self.gaps is None:
77
+ self.gaps = []
78
+ if self.suggestions is None:
79
+ self.suggestions = []
80
+
81
+
82
+ class CareerAgent:
83
+ """Base class for all career agents."""
84
+
85
+ def __init__(self, model_name: str, system_prompt: str, device: str = "auto"):
86
+ self.model_name = model_name
87
+ self.system_prompt = system_prompt
88
+ self.device = device
89
+ self.tokenizer = None
90
+ self.model = None
91
+ self.pipe = None
92
+ self._loaded = False
93
+
94
+ def load(self):
95
+ if self._loaded:
96
+ return
97
+ print(f"[AGENT] Loading {self.model_name}...")
98
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name, trust_remote_code=True)
99
+ if self.tokenizer.pad_token is None:
100
+ self.tokenizer.pad_token = self.tokenizer.eos_token
101
+
102
+ self.model = AutoModelForCausalLM.from_pretrained(
103
+ self.model_name,
104
+ torch_dtype=torch.bfloat16,
105
+ device_map=self.device,
106
+ trust_remote_code=True,
107
+ )
108
+ self.pipe = pipeline(
109
+ "text-generation",
110
+ model=self.model,
111
+ tokenizer=self.tokenizer,
112
+ max_new_tokens=1024,
113
+ temperature=0.7,
114
+ do_sample=True,
115
+ top_p=0.9,
116
+ )
117
+ self._loaded = True
118
+ print(f"[AGENT] {self.model_name} loaded.")
119
+
120
+ def generate(self, user_prompt: str, max_new_tokens: int = 1024) -> str:
121
+ if not self._loaded:
122
+ self.load()
123
+ messages = [
124
+ {"role": "system", "content": self.system_prompt},
125
+ {"role": "user", "content": user_prompt},
126
+ ]
127
+ outputs = self.pipe(messages, max_new_tokens=max_new_tokens, return_full_text=False)
128
+ return outputs[0]["generated_text"]
129
+
130
+
131
+ class ResumeParserAgent(CareerAgent):
132
+ """Extract structured data from resume text."""
133
+
134
+ SYSTEM = (
135
+ "You are an expert resume parser. Extract ALL information from the provided resume "
136
+ "and return ONLY valid JSON with these exact fields: name, email, phone, summary, "
137
+ "experience (array of {title, company, dates, description}), "
138
+ "education (array of {degree, institution, year}), skills (array), certifications (array). "
139
+ "No explanation, no markdown formatting β€” only JSON."
140
+ )
141
+
142
+ def __init__(self, model_name: str, device: str = "auto"):
143
+ super().__init__(model_name, self.SYSTEM, device)
144
+
145
+ def parse(self, resume_text: str) -> ParsedResume:
146
+ prompt = f"Parse the following resume into structured JSON:\n\n{resume_text}"
147
+ raw = self.generate(prompt, max_new_tokens=2048)
148
+
149
+ # Extract JSON
150
+ json_match = re.search(r'\{.*\}', raw, re.DOTALL)
151
+ if json_match:
152
+ try:
153
+ data = json.loads(json_match.group())
154
+ return ParsedResume(
155
+ name=data.get("name", ""),
156
+ email=data.get("email", ""),
157
+ phone=data.get("phone", ""),
158
+ summary=data.get("summary", ""),
159
+ experience=data.get("experience", []),
160
+ education=data.get("education", []),
161
+ skills=data.get("skills", []),
162
+ certifications=data.get("certifications", []),
163
+ raw=raw,
164
+ )
165
+ except json.JSONDecodeError:
166
+ pass
167
+
168
+ return ParsedResume(raw=raw)
169
+
170
+
171
+ class JobMatcherAgent(CareerAgent):
172
+ """Match resume to job description."""
173
+
174
+ SYSTEM = (
175
+ "You are a senior technical recruiter with 15 years of experience. "
176
+ "Assess how well a candidate's resume matches a job description. "
177
+ "Return ONLY valid JSON with: fit_assessment ('Good Fit', 'Partial Fit', 'No Fit'), "
178
+ "score (0-100), strengths (array), gaps (array), suggestions (array)."
179
+ )
180
+
181
+ def __init__(self, model_name: str, device: str = "auto"):
182
+ super().__init__(model_name, self.SYSTEM, device)
183
+
184
+ def score(self, resume_text: str, job_description: str) -> JobFitResult:
185
+ prompt = (
186
+ f"Resume:\n{resume_text}\n\n"
187
+ f"Job Description:\n{job_description}\n\n"
188
+ f"Task: Assess fit. Return JSON only."
189
+ )
190
+ raw = self.generate(prompt, max_new_tokens=1024)
191
+
192
+ json_match = re.search(r'\{.*\}', raw, re.DOTALL)
193
+ if json_match:
194
+ try:
195
+ data = json.loads(json_match.group())
196
+ return JobFitResult(
197
+ fit_assessment=data.get("fit_assessment", ""),
198
+ score=data.get("score", 0),
199
+ strengths=data.get("strengths", []),
200
+ gaps=data.get("gaps", []),
201
+ suggestions=data.get("suggestions", []),
202
+ raw=raw,
203
+ )
204
+ except json.JSONDecodeError:
205
+ pass
206
+
207
+ return JobFitResult(raw=raw)
208
+
209
+
210
+ class CareerAdvisorAgent(CareerAgent):
211
+ """Career path planning, skill gap analysis, interview prep."""
212
+
213
+ SYSTEM = (
214
+ "You are a career advisor with 15 years of experience. "
215
+ "Provide actionable, specific career guidance. Include concrete next steps, timelines, "
216
+ "and measurable goals. Use structured formats (bullet points, numbered lists, markdown headers)."
217
+ )
218
+
219
+ def __init__(self, model_name: str, device: str = "auto"):
220
+ super().__init__(model_name, self.SYSTEM, device)
221
+
222
+ def suggest_path(self, resume_text: str, target_role: Optional[str] = None) -> str:
223
+ prompt = f"Given this resume, what are 3-5 next career steps?\n\n{resume_text}"
224
+ if target_role:
225
+ prompt += f"\n\nTarget role: {target_role}"
226
+ return self.generate(prompt)
227
+
228
+ def interview_prep(self, resume_text: str, role: str) -> str:
229
+ prompt = f"Based on this {role} resume, generate 5 interview questions with preparation tips.\n\n{resume_text}"
230
+ return self.generate(prompt)
231
+
232
+ def skill_gap_analysis(self, resume_text: str, target_role: str) -> str:
233
+ prompt = f"Compare this resume to the requirements for {target_role}. What skills need development?\n\n{resume_text}"
234
+ return self.generate(prompt)
235
+
236
+
237
+ class SalaryNegotiatorAgent(CareerAgent):
238
+ """Salary negotiation strategies."""
239
+
240
+ SYSTEM = (
241
+ "You are a compensation negotiation expert. Provide specific, data-backed strategies. "
242
+ "Include market rate research, anchoring techniques, total compensation framing, "
243
+ "and role-play scripts. Be direct and practical."
244
+ )
245
+
246
+ def __init__(self, model_name: str, device: str = "auto"):
247
+ super().__init__(model_name, self.SYSTEM, device)
248
+
249
+ def strategy(self, current_salary: int, target_role: str, location: str,
250
+ years_experience: int) -> str:
251
+ prompt = (
252
+ f"Current salary: ${current_salary}\n"
253
+ f"Target role: {target_role}\n"
254
+ f"Location: {location}\n"
255
+ f"Years of experience: {years_experience}\n\n"
256
+ f"Provide a complete salary negotiation strategy including market research, "
257
+ f"anchoring techniques, total comp framing, and a ready-to-use script."
258
+ )
259
+ return self.generate(prompt, max_new_tokens=2048)
260
+
261
+
262
+ class CareerOS:
263
+ """
264
+ Orchestrator for the multi-agent Career OS.
265
+ Routes tasks to specialized agents and synthesizes outputs.
266
+ """
267
+
268
+ SYSTEM = (
269
+ "You are the Career OS Orchestrator. You manage a team of specialized career agents:\n"
270
+ "- Resume Parser: extracts structured data from resumes\n"
271
+ "- Job Matcher: assesses resume-job fit\n"
272
+ "- Career Advisor: suggests paths, prepares interviews, analyzes skill gaps\n"
273
+ "- Salary Negotiator: provides compensation strategies\n\n"
274
+ "Your job is to understand the user's request, delegate to the right agent(s), "
275
+ "and synthesize a comprehensive, actionable response. When multiple agents are needed, "
276
+ "call them sequentially and combine their outputs into a single coherent answer."
277
+ )
278
+
279
+ def __init__(self, agent_model: str = "Builder-Neekhil/career-agent-v1",
280
+ device: str = "auto", lazy_load: bool = True):
281
+ self.agent_model = agent_model
282
+ self.device = device
283
+ self.agents: Dict[str, CareerAgent] = {
284
+ "resume_parser": ResumeParserAgent(agent_model, device),
285
+ "job_matcher": JobMatcherAgent(agent_model, device),
286
+ "career_advisor": CareerAdvisorAgent(agent_model, device),
287
+ "salary_negotiator": SalaryNegotiatorAgent(agent_model, device),
288
+ }
289
+ if not lazy_load:
290
+ for agent in self.agents.values():
291
+ agent.load()
292
+
293
+ def process(self, user_request: str, resume_text: Optional[str] = None,
294
+ job_description: Optional[str] = None) -> Dict[str, Any]:
295
+ """
296
+ Main entry point. Routes to appropriate agents based on the request.
297
+ Returns a dict with all agent outputs and a synthesized response.
298
+ """
299
+ result = {
300
+ "request": user_request,
301
+ "agents_called": [],
302
+ "raw_outputs": {},
303
+ "synthesized": "",
304
+ }
305
+
306
+ # Route based on intent detection
307
+ request_lower = user_request.lower()
308
+
309
+ # --- RESUME REVIEW ---
310
+ if any(kw in request_lower for kw in ["review", "feedback", "improve", "resume"]):
311
+ if resume_text:
312
+ parsed = self.agents["resume_parser"].parse(resume_text)
313
+ result["raw_outputs"]["parsed_resume"] = {
314
+ "name": parsed.name,
315
+ "skills": parsed.skills,
316
+ "experience_count": len(parsed.experience),
317
+ }
318
+ result["agents_called"].append("resume_parser")
319
+
320
+ advisor = self.agents["career_advisor"]
321
+ review = advisor.generate(
322
+ f"Please review this resume and give actionable feedback:\n\n{resume_text}"
323
+ )
324
+ result["raw_outputs"]["resume_review"] = review
325
+ result["agents_called"].append("career_advisor")
326
+
327
+ result["synthesized"] = review
328
+
329
+ # --- JOB FIT ASSESSMENT ---
330
+ elif any(kw in request_lower for kw in ["fit", "match", "job", "description"]):
331
+ if resume_text and job_description:
332
+ fit_result = self.agents["job_matcher"].score(resume_text, job_description)
333
+ result["raw_outputs"]["job_fit"] = {
334
+ "score": fit_result.score,
335
+ "assessment": fit_result.fit_assessment,
336
+ "strengths": fit_result.strengths,
337
+ "gaps": fit_result.gaps,
338
+ "suggestions": fit_result.suggestions,
339
+ }
340
+ result["agents_called"].append("job_matcher")
341
+
342
+ # Also get career advice on closing gaps
343
+ gap_analysis = self.agents["career_advisor"].skill_gap_analysis(
344
+ resume_text, job_description[:200]
345
+ )
346
+ result["raw_outputs"]["gap_analysis"] = gap_analysis
347
+ result["agents_called"].append("career_advisor")
348
+
349
+ result["synthesized"] = (
350
+ f"## Job Fit Assessment\n\n"
351
+ f"**Score:** {fit_result.score}/100 β€” {fit_result.fit_assessment}\n\n"
352
+ f"**Strengths:**\n" + "\n".join(f"- {s}" for s in fit_result.strengths) + "\n\n"
353
+ f"**Gaps:**\n" + "\n".join(f"- {g}" for g in fit_result.gaps) + "\n\n"
354
+ f"**Suggestions:**\n" + "\n".join(f"- {s}" for s in fit_result.suggestions) + "\n\n"
355
+ f"**Gap Analysis:**\n{gap_analysis[:500]}..."
356
+ )
357
+
358
+ # --- CAREER PATH ---
359
+ elif any(kw in request_lower for kw in ["career path", "next steps", "future", "grow"]):
360
+ if resume_text:
361
+ path = self.agents["career_advisor"].suggest_path(resume_text)
362
+ result["raw_outputs"]["career_path"] = path
363
+ result["agents_called"].append("career_advisor")
364
+ result["synthesized"] = path
365
+
366
+ # --- INTERVIEW PREP ---
367
+ elif any(kw in request_lower for kw in ["interview", "questions", "prep"]):
368
+ role = self._extract_role(user_request)
369
+ if resume_text:
370
+ prep = self.agents["career_advisor"].interview_prep(resume_text, role or "professional")
371
+ result["raw_outputs"]["interview_prep"] = prep
372
+ result["agents_called"].append("career_advisor")
373
+ result["synthesized"] = prep
374
+
375
+ # --- SALARY NEGOTIATION ---
376
+ elif any(kw in request_lower for kw in ["salary", "negotiate", "compensation", "offer"]):
377
+ # Try to extract info from context
378
+ strategy = self.agents["salary_negotiator"].strategy(
379
+ current_salary=100000,
380
+ target_role="Senior Software Engineer",
381
+ location="San Francisco, CA",
382
+ years_experience=5,
383
+ )
384
+ result["raw_outputs"]["salary_strategy"] = strategy
385
+ result["agents_called"].append("salary_negotiator")
386
+ result["synthesized"] = strategy
387
+
388
+ # --- DEFAULT: FULL CAREER CONSULTATION ---
389
+ else:
390
+ if resume_text and job_description:
391
+ # Full pipeline
392
+ parsed = self.agents["resume_parser"].parse(resume_text)
393
+ fit = self.agents["job_matcher"].score(resume_text, job_description)
394
+ path = self.agents["career_advisor"].suggest_path(resume_text)
395
+
396
+ result["raw_outputs"]["parsed_resume"] = parsed.__dict__
397
+ result["raw_outputs"]["job_fit"] = fit.__dict__
398
+ result["raw_outputs"]["career_path"] = path
399
+ result["agents_called"].extend(["resume_parser", "job_matcher", "career_advisor"])
400
+
401
+ result["synthesized"] = (
402
+ f"## Career OS Full Report\n\n"
403
+ f"### 1. Resume Summary\n"
404
+ f"- **Name:** {parsed.name}\n"
405
+ f"- **Skills:** {', '.join(parsed.skills[:5])}\n"
406
+ f"- **Experience:** {len(parsed.experience)} roles\n\n"
407
+ f"### 2. Job Fit\n"
408
+ f"- **Score:** {fit.score}/100 ({fit.fit_assessment})\n\n"
409
+ f"### 3. Career Path\n"
410
+ f"{path[:800]}..."
411
+ )
412
+ else:
413
+ # Direct response
414
+ result["synthesized"] = self.agents["career_advisor"].generate(user_request)
415
+ result["agents_called"].append("career_advisor")
416
+
417
+ return result
418
+
419
+ def _extract_role(self, text: str) -> Optional[str]:
420
+ """Extract role name from request text."""
421
+ common_roles = [
422
+ "software engineer", "data scientist", "product manager",
423
+ "designer", "marketing manager", "sales manager", "analyst",
424
+ "consultant", "engineer", "manager", "director", "vp"
425
+ ]
426
+ text_lower = text.lower()
427
+ for role in common_roles:
428
+ if role in text_lower:
429
+ return role.title()
430
+ return None
431
+
432
+ def full_pipeline(self, resume_text: str, job_description: Optional[str] = None,
433
+ target_role: Optional[str] = None) -> Dict[str, Any]:
434
+ """
435
+ Run the complete Career OS pipeline:
436
+ Parse β†’ Match (if JD) β†’ Path β†’ Interview Prep
437
+ """
438
+ print("[CAREER OS] Running full pipeline...")
439
+ results = {}
440
+
441
+ # 1. Parse resume
442
+ print("[1/4] Parsing resume...")
443
+ parsed = self.agents["resume_parser"].parse(resume_text)
444
+ results["parsed_resume"] = parsed
445
+
446
+ # 2. Match to job
447
+ if job_description:
448
+ print("[2/4] Matching to job description...")
449
+ fit = self.agents["job_matcher"].score(resume_text, job_description)
450
+ results["job_fit"] = fit
451
+ else:
452
+ print("[2/4] Skipping job match (no JD provided)...")
453
+
454
+ # 3. Career path
455
+ print("[3/4] Generating career path...")
456
+ path = self.agents["career_advisor"].suggest_path(resume_text, target_role)
457
+ results["career_path"] = path
458
+
459
+ # 4. Interview prep
460
+ role = target_role or parsed.experience[0].get("title", "professional") if parsed.experience else "professional"
461
+ print(f"[4/4] Generating interview prep for {role}...")
462
+ prep = self.agents["career_advisor"].interview_prep(resume_text, role)
463
+ results["interview_prep"] = prep
464
+
465
+ print("[CAREER OS] Pipeline complete!")
466
+ return results
467
+
468
+
469
+ # ── CLI DEMO ──────────────────────────────────────────────────────────
470
+
471
+ if __name__ == "__main__":
472
+ import argparse
473
+ parser = argparse.ArgumentParser()
474
+ parser.add_argument("--model", default="Builder-Neekhil/career-agent-v1")
475
+ parser.add_argument("--task", default="review", choices=["review", "fit", "path", "interview", "full"])
476
+ parser.add_argument("--resume", default="")
477
+ parser.add_argument("--job", default="")
478
+ args = parser.parse_args()
479
+
480
+ cos = CareerOS(agent_model=args.model)
481
+
482
+ demo_resume = args.resume or (
483
+ "John Doe\nSoftware Engineer\n5 years experience in Python, React, AWS. "
484
+ "Built REST APIs with FastAPI and microservices. Led a team of 3 developers. "
485
+ "BSc Computer Science, Stanford University.\n"
486
+ "Skills: Python, JavaScript, AWS, Docker, Kubernetes, PostgreSQL, Redis\n"
487
+ "Certifications: AWS Solutions Architect, Certified Scrum Master"
488
+ )
489
+
490
+ demo_job = args.job or (
491
+ "Senior Software Engineer\nRequirements:\n- 5+ years Python experience\n"
492
+ "- Microservices architecture\n- Team leadership\n- AWS/GCP\n- Docker, Kubernetes\n"
493
+ "- BSc in Computer Science or equivalent"
494
+ )
495
+
496
+ if args.task == "review":
497
+ result = cos.process("Review my resume", resume_text=demo_resume)
498
+ print(result["synthesized"])
499
+
500
+ elif args.task == "fit":
501
+ result = cos.process("Assess job fit", resume_text=demo_resume, job_description=demo_job)
502
+ print(result["synthesized"])
503
+
504
+ elif args.task == "path":
505
+ result = cos.process("Career path", resume_text=demo_resume)
506
+ print(result["synthesized"])
507
+
508
+ elif args.task == "interview":
509
+ result = cos.process("Interview prep", resume_text=demo_resume)
510
+ print(result["synthesized"])
511
+
512
+ elif args.task == "full":
513
+ results = cos.full_pipeline(demo_resume, demo_job)
514
+ print("\n=== PARSED RESUME ===")
515
+ print(f"Name: {results['parsed_resume'].name}")
516
+ print(f"Skills: {results['parsed_resume'].skills}")
517
+ if 'job_fit' in results:
518
+ print(f"\n=== JOB FIT ===")
519
+ print(f"Score: {results['job_fit'].score}/100")
520
+ print(f"\n=== CAREER PATH ===")
521
+ print(results['career_path'][:500])
522
+ print(f"\n=== INTERVIEW PREP ===")
523
+ print(results['interview_prep'][:500])