sandeeppanem commited on
Commit
87fab71
·
1 Parent(s): ded28a2
Files changed (4) hide show
  1. README.md +53 -9
  2. app.py +531 -0
  3. qwen3-resume-parser-Q5_K_M.gguf +3 -0
  4. requirements.txt +15 -0
README.md CHANGED
@@ -1,13 +1,57 @@
1
  ---
2
- title: Qwen3 Resume Parser
3
- emoji: 👁
4
- colorFrom: red
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 6.3.0
8
- app_file: app.py
9
- pinned: false
10
- short_description: Extract structured JSON from resumes using fine-tuned model
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Qwen3 Resume Structured Information Extraction
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 4.0.0
8
+ app_port: 7860
9
+ python_version: "3.11"
 
10
  ---
11
 
12
+ # Qwen3 Resume Structured Information Extraction
13
+
14
+ Extract structured information from resumes using a fine-tuned Qwen3-0.6B model, optimized for CPU inference with GGUF format and Q5_K_M quantization.
15
+
16
+ ## Features
17
+
18
+ - **Fast CPU Inference**: Uses llama.cpp with Q5_K_M quantization for 7-15x faster inference
19
+ - **Structured JSON Output**: Extracts resume information into structured JSON format
20
+ - **Optimized Model**: Merged LoRA adapter with Q5_K_M quantization (~400-500MB)
21
+
22
+ ## Model Information
23
+
24
+ - **Base Model**: [Qwen/Qwen3-0.6B](https://huggingface.co/Qwen/Qwen3-0.6B)
25
+ - **Fine-tuned Model**: [sandeeppanem/qwen3-0.6b-resume-json](https://huggingface.co/sandeeppanem/qwen3-0.6b-resume-json)
26
+ - **Training Dataset**: [sandeeppanem/resume-json-extraction-5k](https://huggingface.co/datasets/sandeeppanem/resume-json-extraction-5k)
27
+ - **Format**: GGUF Q5_K_M (optimized for CPU)
28
+
29
+ ## Performance
30
+
31
+ - **Previous (transformers)**: ~77 seconds per request on CPU
32
+ - **Current (GGUF)**: ~5-10 seconds per request on CPU
33
+ - **Improvement**: 7-15x faster
34
+
35
+ ## Usage
36
+
37
+ 1. Paste your resume text in the input box
38
+ 2. Click "Parse Resume"
39
+ 3. View the extracted structured JSON output
40
+
41
+ ## Deployment
42
+
43
+ This Space uses:
44
+ - **Gradio** for the web interface
45
+ - **llama-cpp-python** for GGUF model inference
46
+ - **Q5_K_M quantization** for optimal speed/quality balance
47
+
48
+ The GGUF model file (`qwen3-resume-parser-Q5_K_M.gguf`) should be included in this Space repository (use Git LFS if >100MB).
49
+
50
+ ## Links
51
+
52
+ - **🚀 Live Demo**: [Try the Resume Parser](https://huggingface.co/spaces/sandeeppanem/qwen3-resume-parser) (this Space)
53
+ - **📦 Model**: [sandeeppanem/qwen3-0.6b-resume-json](https://huggingface.co/sandeeppanem/qwen3-0.6b-resume-json)
54
+ - **📊 Dataset**: [sandeeppanem/resume-json-extraction-5k](https://huggingface.co/datasets/sandeeppanem/resume-json-extraction-5k)
55
+ - **💻 Repository**: [qwen3-resume-extraction](https://github.com/sandeeppanem/qwen3-resume-extraction)
56
+
57
+
app.py ADDED
@@ -0,0 +1,531 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Gradio app for Qwen3 Resume Parser using llama.cpp (GGUF format).
4
+
5
+ Optimized for CPU inference with GGUF quantized model.
6
+ Uses llama-cpp-python for fast CPU inference.
7
+ """
8
+
9
+ import gradio as gr
10
+ import hashlib
11
+ import json
12
+ import os
13
+ import re
14
+ from collections import OrderedDict
15
+ from pathlib import Path
16
+
17
+ # Model configuration
18
+ MODEL_PATH = "qwen3-resume-parser-Q5_K_M.gguf"
19
+ # If model is in a subdirectory, adjust path accordingly
20
+ # MODEL_PATH = "models/qwen3-resume-parser-Q5_K_M.gguf"
21
+
22
+ # Global variables for model caching
23
+ _model = None
24
+
25
+ # Shared result cache (key: hash of resume text, value: (formatted_json, raw_output))
26
+ # Using OrderedDict for FIFO eviction when cache is full
27
+ _result_cache = OrderedDict()
28
+ MAX_CACHE_SIZE = 100 # Keep last 100 results
29
+
30
+
31
+ def format_qwen3_prompt(resume_text: str) -> str:
32
+ """Format prompt for Qwen3 chat template."""
33
+ system_content = (
34
+ "You are an expert resume parser. "
35
+ "Extract structured information from resumes and return ONLY valid JSON. "
36
+ "Do not include explanations or extra text."
37
+ )
38
+ user_content = f"Resume:\n{resume_text.strip()}"
39
+ prompt = (
40
+ f"<|im_start|>system\n{system_content}<|im_end|>\n"
41
+ f"<|im_start|>user\n{user_content}<|im_end|>\n"
42
+ f"<|im_start|>assistant\n"
43
+ )
44
+ return prompt
45
+
46
+
47
+ def load_model():
48
+ """Load GGUF model using llama-cpp-python (loads once at startup)."""
49
+ global _model
50
+
51
+ if _model is not None:
52
+ return _model
53
+
54
+ try:
55
+ from llama_cpp import Llama
56
+ except ImportError:
57
+ raise ImportError(
58
+ "llama-cpp-python not installed. "
59
+ "Install with: pip install llama-cpp-python"
60
+ )
61
+
62
+ # Try multiple possible paths for the model file
63
+ script_dir = Path(__file__).parent
64
+ possible_paths = [
65
+ Path(MODEL_PATH), # Current directory
66
+ script_dir / MODEL_PATH, # Same directory as app.py
67
+ script_dir.parent / MODEL_PATH, # Parent directory
68
+ ]
69
+
70
+ model_path = None
71
+ for path in possible_paths:
72
+ if path.exists() and path.is_file():
73
+ model_path = path
74
+ print(f"Found model at: {model_path.absolute()}")
75
+ break
76
+
77
+ if model_path is None:
78
+ # List available files for debugging
79
+ print(f"Current directory: {Path.cwd()}")
80
+ print(f"Script directory: {script_dir.absolute()}")
81
+ print(f"Files in script directory: {list(script_dir.iterdir())}")
82
+ raise FileNotFoundError(
83
+ f"GGUF model not found. Tried: {[str(p) for p in possible_paths]}\n"
84
+ f"Make sure {MODEL_PATH} is in the Space repository."
85
+ )
86
+
87
+ # Dynamically determine thread count based on available CPUs
88
+ cpu_count = os.cpu_count() or 2
89
+ n_threads = min(cpu_count, 8) # Use available CPUs, cap at 8
90
+ print(f"Detected {cpu_count} CPUs, using {n_threads} threads")
91
+
92
+ try:
93
+ print(f"Loading model from: {model_path.absolute()}")
94
+ print(f"Model file size: {model_path.stat().st_size / (1024*1024):.2f} MB")
95
+
96
+ # Check llama-cpp-python version
97
+ try:
98
+ import llama_cpp
99
+ print(f"llama-cpp-python version: {llama_cpp.__version__ if hasattr(llama_cpp, '__version__') else 'unknown'}")
100
+ except:
101
+ pass
102
+
103
+ # Try loading with minimal parameters first, then add optimizations
104
+ print("Attempting to load model...")
105
+
106
+ # Optimized parameters for faster inference
107
+ _model = Llama(
108
+ model_path=str(model_path),
109
+ n_ctx=2560,
110
+ n_threads=n_threads,
111
+ n_batch=128, # Testing with reduced batch size
112
+ n_gpu_layers=0,
113
+ chat_format=None, # Disable chat format parsing for speed
114
+ verbose=True,
115
+ )
116
+ print("✓ Model loaded with optimized parameters")
117
+
118
+ print(f"✓ Model loaded successfully! (using {n_threads} threads)")
119
+
120
+ except Exception as e:
121
+ error_type = type(e).__name__
122
+ error_msg = str(e)
123
+ full_error = f"Failed to load model from {model_path}: {error_type}: {error_msg}"
124
+ print(f"❌ {full_error}")
125
+
126
+ # Provide helpful suggestions based on error
127
+ if "mmap" in error_msg.lower() or "memory" in error_msg.lower():
128
+ print("\n💡 Suggestion: Try disabling mmap or reducing context size")
129
+ elif "format" in error_msg.lower() or "invalid" in error_msg.lower():
130
+ print("\n💡 Suggestion: Model file might be corrupted or incompatible format")
131
+ print(" Try regenerating the GGUF file or check llama-cpp-python version compatibility")
132
+ elif "permission" in error_msg.lower():
133
+ print("\n💡 Suggestion: Check file permissions")
134
+
135
+ raise RuntimeError(full_error) from e
136
+
137
+ return _model
138
+
139
+
140
+ def _format_incomplete_json(text: str) -> str:
141
+ """Format incomplete JSON for visibility during streaming."""
142
+ if not text or not text.strip():
143
+ return text
144
+
145
+ formatted = text
146
+
147
+ # First, ensure proper spacing around colons (makes it more readable)
148
+ formatted = re.sub(r':"', ': "', formatted)
149
+ formatted = re.sub(r':(\d+)', r': \1', formatted)
150
+ formatted = re.sub(r':(true|false|null)', r': \1', formatted)
151
+ formatted = re.sub(r':\{', ': {', formatted)
152
+ formatted = re.sub(r':\[', ': [', formatted)
153
+ formatted = re.sub(r',\s*"', ',\n "', formatted)
154
+ # Pattern: comma followed by number
155
+ formatted = re.sub(r',\s*(\d+)', r',\n \1', formatted)
156
+ formatted = re.sub(r',\s*(true|false|null)', r',\n \1', formatted)
157
+ # Pattern: comma followed by opening brace/array
158
+ formatted = re.sub(r',\s*(\{|\[)', r',\n \1', formatted)
159
+ formatted = re.sub(r'\{\s*"', '{\n "', formatted)
160
+
161
+ # Add newline before closing brace (if it's on same line with content)
162
+ # But be careful not to break strings
163
+ formatted = re.sub(r'([^}\s"])\s*\}', r'\1\n}', formatted)
164
+ formatted = re.sub(r'\n\n+', '\n', formatted)
165
+ formatted = re.sub(r' +', ' ', formatted)
166
+ return formatted
167
+
168
+
169
+ def parse_resume_stream(resume_text: str):
170
+ """Parse resume text and stream structured JSON as it's generated."""
171
+ if not resume_text or not resume_text.strip():
172
+ yield "⚠️ Please provide resume text.", ""
173
+ return
174
+
175
+ # Normalize resume text for caching (strip whitespace)
176
+ normalized_text = resume_text.strip()
177
+
178
+ # Create hash key for cache lookup
179
+ cache_key = hashlib.md5(normalized_text.encode('utf-8')).hexdigest()
180
+
181
+ # Check cache first
182
+ if cache_key in _result_cache:
183
+ # Move to end (most recently used) for LRU-like behavior
184
+ cached_json, cached_raw = _result_cache.pop(cache_key)
185
+ _result_cache[cache_key] = (cached_json, cached_raw)
186
+ yield cached_json, cached_raw
187
+ return
188
+
189
+ try:
190
+ model = load_model()
191
+
192
+ MAX_RESUME_CHARS = 4000
193
+ if len(normalized_text) > MAX_RESUME_CHARS:
194
+ truncated = normalized_text[:MAX_RESUME_CHARS]
195
+ last_space = truncated.rfind(' ', MAX_RESUME_CHARS - 200, MAX_RESUME_CHARS)
196
+ if last_space > MAX_RESUME_CHARS - 500:
197
+ truncated = truncated[:last_space]
198
+ normalized_text = truncated + "..."
199
+
200
+ prompt = format_qwen3_prompt(normalized_text)
201
+ accumulated_text = ""
202
+
203
+ stream = model(
204
+ prompt,
205
+ max_tokens=350,
206
+ temperature=0,
207
+ stop=["<|im_end|>", "<|endoftext|>"],
208
+ echo=False,
209
+ stream=True,
210
+ )
211
+
212
+ # Process streamed tokens
213
+ final_json = None
214
+ final_raw = None
215
+ chunk_count = 0
216
+
217
+ for chunk in stream:
218
+ if "choices" in chunk and len(chunk["choices"]) > 0:
219
+ delta = chunk["choices"][0].get("text", "")
220
+ if delta:
221
+ accumulated_text += delta
222
+ chunk_count += 1
223
+
224
+ # Only do expensive operations every 5 chunks or if we have enough text
225
+ # This reduces overhead during streaming
226
+ if chunk_count % 5 == 0 or len(accumulated_text) > 50:
227
+ cleaned_text = accumulated_text
228
+ cleaned_text = re.sub(r'<think>.*?</think>', '', cleaned_text, flags=re.DOTALL)
229
+ cleaned_text = re.sub(r'</?redacted_reasoning>', '', cleaned_text)
230
+ cleaned_text = re.sub(r'</?think>', '', cleaned_text)
231
+ cleaned_text = re.sub(r'\n\s*\n+', '\n', cleaned_text)
232
+ cleaned_text = cleaned_text.strip()
233
+
234
+ try:
235
+ parsed_json = json.loads(cleaned_text)
236
+ formatted_json = json.dumps(parsed_json, indent=2, ensure_ascii=False)
237
+ final_json = formatted_json
238
+ final_raw = cleaned_text
239
+ yield formatted_json, cleaned_text
240
+ except json.JSONDecodeError:
241
+ formatted_incomplete = _format_incomplete_json(cleaned_text)
242
+ yield formatted_incomplete, cleaned_text
243
+
244
+
245
+ # Final processing after stream completes
246
+ assistant_response = accumulated_text.strip()
247
+ assistant_response = re.sub(r'<think>.*?</think>', '', assistant_response, flags=re.DOTALL)
248
+ assistant_response = re.sub(r'</?redacted_reasoning>', '', assistant_response)
249
+ assistant_response = re.sub(r'</?think>', '', assistant_response)
250
+ assistant_response = re.sub(r'\n\s*\n+', '\n', assistant_response)
251
+ assistant_response = assistant_response.strip()
252
+
253
+ try:
254
+ parsed_json = json.loads(assistant_response)
255
+ formatted_json = json.dumps(parsed_json, indent=2, ensure_ascii=False)
256
+ final_json = formatted_json
257
+ final_raw = assistant_response
258
+ yield formatted_json, assistant_response
259
+ except json.JSONDecodeError:
260
+ yield (
261
+ f"⚠️ Model output is not valid JSON:\n\n{assistant_response}",
262
+ assistant_response,
263
+ )
264
+ return # Don't cache invalid JSON
265
+
266
+ # Cache the result for future users (only if we got valid JSON)
267
+ if final_json and final_raw:
268
+ # Enforce cache size limit (FIFO eviction)
269
+ if len(_result_cache) >= MAX_CACHE_SIZE:
270
+ # Remove oldest entry (first item in OrderedDict)
271
+ _result_cache.popitem(last=False)
272
+
273
+ # Add new result to cache
274
+ _result_cache[cache_key] = (final_json, final_raw)
275
+
276
+ except Exception as e:
277
+ yield f"❌ Error: {str(e)}", ""
278
+
279
+
280
+ def parse_resume(resume_text: str) -> tuple[str, str]:
281
+ """Parse resume text and return structured JSON (non-streaming version)."""
282
+ result = None
283
+ for result in parse_resume_stream(resume_text):
284
+ pass
285
+ return result if result else ("⚠️ No output generated", "")
286
+
287
+
288
+ # Load model at startup
289
+ try:
290
+ # Load model at startup (will show error in logs if it fails)
291
+ try:
292
+ load_model()
293
+ except Exception as e:
294
+ print(f"⚠️ Warning: Could not load model at startup: {e}")
295
+ print("Model will be loaded on first use.")
296
+ except Exception as e:
297
+ print(f"Error loading model: {e}")
298
+
299
+
300
+ # Gradio Interface
301
+ def create_interface():
302
+ """Create and return Gradio interface."""
303
+
304
+ with gr.Blocks(title="Qwen3 Resume Structured Information Extraction", theme=gr.themes.Soft()) as demo:
305
+ gr.Markdown(
306
+ """
307
+ # 🚀 Qwen3 Resume Structured Information Extraction
308
+
309
+ Extract structured information from resumes using fine-tuned Qwen3-0.6B model.
310
+ **Optimized for CPU inference using llama.cpp and Q5_K_M quantization.**
311
+
312
+ **How to use:**
313
+ 1. Paste your resume text in the text box below
314
+ 2. Click "Parse Resume"
315
+ 3. View the extracted structured JSON output
316
+
317
+ **Model:** [sandeeppanem/qwen3-0.6b-resume-json](https://huggingface.co/sandeeppanem/qwen3-0.6b-resume-json)
318
+ **Dataset:** [sandeeppanem/resume-json-extraction-5k](https://huggingface.co/datasets/sandeeppanem/resume-json-extraction-5k)
319
+ **Repository:** [qwen3-resume-extraction](https://github.com/sandeeppanem/qwen3-resume-extraction)
320
+ **Format:** GGUF Q5_K_M (optimized for CPU)
321
+ """
322
+ )
323
+
324
+ with gr.Row():
325
+ with gr.Column(scale=1):
326
+ resume_input = gr.Textbox(
327
+ label="Resume Text",
328
+ placeholder="Paste your resume text here...",
329
+ lines=15,
330
+ max_lines=20,
331
+ )
332
+ parse_btn = gr.Button("Parse Resume", variant="primary", size="lg")
333
+
334
+ gr.Markdown(
335
+ """
336
+ **Example:**
337
+ ```
338
+ Senior IT Project Manager with 10+ years experience leading enterprise migrations.
339
+ Skills: Python, SQL, AWS, Agile. Location: Chicago, IL.
340
+ Experience: Project Manager at Acme Corp (2019-2024).
341
+ Education: MS Computer Science.
342
+ ```
343
+ """
344
+ )
345
+
346
+ with gr.Column(scale=1):
347
+ json_output = gr.Code(
348
+ label="Extracted JSON",
349
+ language="json",
350
+ lines=20,
351
+ )
352
+ raw_output = gr.Textbox(
353
+ label="Raw Model Output",
354
+ lines=5,
355
+ visible=False,
356
+ )
357
+
358
+ # Examples - diverse resume samples
359
+ example_resumes = [
360
+ """Senior IT Project Manager with 10+ years experience leading enterprise migrations.
361
+ Skills: Python, SQL, AWS, Agile. Location: Chicago, IL.
362
+ Experience: Project Manager at Acme Corp (2019-2024).
363
+ Education: MS Computer Science.""",
364
+
365
+ """Software Engineer
366
+ John Smith
367
+ Email: john.smith@email.com | Phone: (555) 123-4567 | Location: San Francisco, CA
368
+
369
+ PROFESSIONAL SUMMARY
370
+ Full Stack Developer with 5 years of experience building scalable web applications.
371
+ Expertise in React, Node.js, Python, and cloud technologies.
372
+
373
+ TECHNICAL SKILLS
374
+ Languages: JavaScript, Python, TypeScript, Java
375
+ Frameworks: React, Node.js, Express, Django, Spring Boot
376
+ Cloud: AWS (EC2, S3, Lambda), Docker, Kubernetes
377
+ Databases: PostgreSQL, MongoDB, Redis
378
+
379
+ PROFESSIONAL EXPERIENCE
380
+ Senior Software Engineer | TechCorp Inc. | San Francisco, CA | 2021 - Present
381
+ - Developed microservices architecture serving 1M+ users
382
+ - Led team of 3 junior developers
383
+ - Reduced API response time by 40% through optimization
384
+
385
+ Software Engineer | StartupXYZ | San Francisco, CA | 2019 - 2021
386
+ - Built customer-facing React applications
387
+ - Implemented CI/CD pipelines using Jenkins
388
+
389
+ EDUCATION
390
+ Bachelor of Science in Computer Science
391
+ University of California, Berkeley | 2019""",
392
+
393
+ """Data Scientist
394
+ Sarah Johnson
395
+ sarah.johnson@email.com | (555) 987-6543 | New York, NY
396
+
397
+ SUMMARY
398
+ Data Scientist with 7 years of experience in machine learning, statistical analysis, and big data.
399
+ Specialized in NLP and computer vision applications.
400
+
401
+ SKILLS
402
+ Programming: Python, R, SQL, Scala
403
+ ML/AI: TensorFlow, PyTorch, scikit-learn, XGBoost
404
+ Tools: Spark, Hadoop, Tableau, Jupyter
405
+ Cloud: AWS SageMaker, Azure ML
406
+
407
+ EXPERIENCE
408
+ Lead Data Scientist | DataTech Solutions | New York, NY | 2020 - Present
409
+ - Built recommendation system increasing user engagement by 35%
410
+ - Developed NLP models for sentiment analysis
411
+ - Managed team of 4 data scientists
412
+
413
+ Data Scientist | Analytics Pro | New York, NY | 2018 - 2020
414
+ - Created predictive models for customer churn
415
+ - Analyzed large datasets using Spark
416
+
417
+ EDUCATION
418
+ Master of Science in Data Science | Columbia University | 2018
419
+ Bachelor of Science in Statistics | NYU | 2016""",
420
+
421
+ """Marketing Manager
422
+ Michael Chen
423
+ michael.chen@email.com | (555) 456-7890 | Los Angeles, CA
424
+
425
+ PROFESSIONAL PROFILE
426
+ Strategic Marketing Manager with 8+ years driving brand growth and digital marketing campaigns.
427
+ Expert in SEO, content marketing, and social media strategy.
428
+
429
+ CORE COMPETENCIES
430
+ Digital Marketing, SEO/SEM, Content Strategy, Social Media Management,
431
+ Google Analytics, HubSpot, Marketo, Brand Management
432
+
433
+ PROFESSIONAL EXPERIENCE
434
+ Marketing Manager | BrandCo | Los Angeles, CA | 2019 - Present
435
+ - Increased website traffic by 150% through SEO optimization
436
+ - Launched successful social media campaigns reaching 2M+ impressions
437
+ - Managed $500K annual marketing budget
438
+
439
+ Marketing Specialist | Growth Agency | Los Angeles, CA | 2016 - 2019
440
+ - Developed content marketing strategies
441
+ - Executed email marketing campaigns with 25% open rate
442
+
443
+ EDUCATION
444
+ Master of Business Administration (MBA) | UCLA | 2016
445
+ Bachelor of Arts in Communications | USC | 2014""",
446
+
447
+ """Product Manager
448
+ Emily Rodriguez
449
+ emily.rodriguez@email.com | (555) 234-5678 | Seattle, WA
450
+
451
+ OVERVIEW
452
+ Product Manager with 6 years of experience in B2B SaaS products.
453
+ Led product launches from concept to market, working with engineering and design teams.
454
+
455
+ KEY SKILLS
456
+ Product Strategy, Agile/Scrum, User Research, A/B Testing,
457
+ Roadmap Planning, Stakeholder Management, JIRA, Figma
458
+
459
+ WORK EXPERIENCE
460
+ Senior Product Manager | CloudSoft | Seattle, WA | 2020 - Present
461
+ - Launched 3 major product features, increasing revenue by $2M annually
462
+ - Conducted user research and usability testing
463
+ - Managed product roadmap and prioritized features
464
+
465
+ Product Manager | StartupHub | Seattle, WA | 2018 - 2020
466
+ - Owned product lifecycle for mobile application
467
+ - Collaborated with cross-functional teams
468
+
469
+ EDUCATION
470
+ Master of Science in Product Management | University of Washington | 2018
471
+ Bachelor of Science in Business Administration | Washington State University | 2016""",
472
+
473
+ """DevOps Engineer
474
+ David Kim
475
+ david.kim@email.com | (555) 345-6789 | Austin, TX
476
+
477
+ SUMMARY
478
+ DevOps Engineer with 4 years of experience in CI/CD, infrastructure automation, and cloud architecture.
479
+ Proven track record of improving deployment efficiency and system reliability.
480
+
481
+ TECHNICAL SKILLS
482
+ Cloud Platforms: AWS, Azure, GCP
483
+ CI/CD: Jenkins, GitLab CI, GitHub Actions, CircleCI
484
+ Infrastructure: Terraform, Ansible, CloudFormation
485
+ Containers: Docker, Kubernetes, ECS
486
+ Monitoring: Prometheus, Grafana, ELK Stack
487
+ Scripting: Bash, Python, PowerShell
488
+
489
+ EXPERIENCE
490
+ DevOps Engineer | CloudInfra Inc. | Austin, TX | 2021 - Present
491
+ - Reduced deployment time from 2 hours to 15 minutes
492
+ - Implemented infrastructure as code using Terraform
493
+ - Set up monitoring and alerting systems
494
+
495
+ Junior DevOps Engineer | TechStart | Austin, TX | 2020 - 2021
496
+ - Maintained CI/CD pipelines
497
+ - Managed cloud infrastructure on AWS
498
+
499
+ EDUCATION
500
+ Bachelor of Science in Computer Engineering
501
+ University of Texas at Austin | 2020"""
502
+ ]
503
+
504
+ gr.Examples(
505
+ examples=[[resume] for resume in example_resumes],
506
+ inputs=resume_input,
507
+ label="Select a sample resume:",
508
+ )
509
+
510
+ # Connect button with streaming
511
+ parse_btn.click(
512
+ fn=parse_resume_stream, # Use streaming version
513
+ inputs=resume_input,
514
+ outputs=[json_output, raw_output],
515
+ )
516
+
517
+ # Also parse on Enter key with streaming
518
+ resume_input.submit(
519
+ fn=parse_resume_stream, # Use streaming version
520
+ inputs=resume_input,
521
+ outputs=[json_output, raw_output],
522
+ )
523
+
524
+ return demo
525
+
526
+
527
+ if __name__ == "__main__":
528
+ demo = create_interface()
529
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
530
+
531
+
qwen3-resume-parser-Q5_K_M.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:559b7a3b34eeb4773699f0bf954476e5c5b0c0c075d3ee6ad0bc9941d1879744
3
+ size 444414688
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Requirements for Hugging Face Space deployment
2
+ # Optimized for CPU inference with llama.cpp (GGUF format)
3
+
4
+ # Gradio for web interface
5
+ # Use stable 4.x version
6
+ gradio>=4.0.0,<5.0.0
7
+
8
+ # huggingface_hub: Pin to version with HfFolder (required by Gradio)
9
+ # HfFolder was removed in huggingface_hub >= 0.23.0
10
+ huggingface_hub>=0.20.0,<0.23.0
11
+
12
+ # llama-cpp-python: Use pre-built wheel (includes Qwen3 support)
13
+ llama-cpp-python==0.3.16
14
+
15
+