Subes tu currículum, el sistema genera búsquedas tipo LinkedIn, raspa las ofertas y te devuelve una corta lista con justificación por puesto. ¿Suena mejor que 50 resultados sin contexto? Eso es justo lo que presenta Job Searcher: un pipeline compacto para que la búsqueda laboral deje de ser un filtro manual y pase a ser un proceso con razonamiento humanoide y trazable.
Qué hace Job Searcher
El flujo de trabajo tiene tres pasos claros y repetibles. No es magia: es arquitectura y datos bien pensados.
-
Queries. El estudiante lee el currículum y las preferencias (tipo de trabajo, modalidad, ubicación, notas) y escribe un pequeño conjunto de consultas en estilo LinkedIn, razoando en voz alta cada elección.
-
Search. Esas consultas se envían a LinkedIn mediante JobSpy, una pasarela de scraping que devuelve las vacantes reales que coinciden con cada query.
-
Scoring. Para cada (currículum, oferta) el modelo produce una puntuación en cinco dimensiones: skills, relevancia de experiencia, educación y certificaciones, ajuste por industria/dominio y alineación de seniority. Además escribe una frase justificando cada dimensión.
¿Qué recibes al final? No una lista larga, sino una shortlist pequeña con razonamiento defendible: puedes leer por qué el segundo puesto supera al tercero.
Arquitectura y modelos (parte técnica)
El proyecto usa una estrategia teacher-student. La "maestra" es DeepSeek V4 Pro: fuerte en razonamiento estructurado y útil para generar etiquetas a gran escala en offline. La "estudiante" es Qwen3-8B, lo suficientemente pequeña para caber en una sola slice ZeroGPU una vez cuantizada a Q4_K_M, pero capaz de absorber el juicio estructurado de la maestra mediante distilación.
Datos y loop cerrado:
- Resumes: 2,500 (basados en Divyaamith/Kaggle-Resume).
- Queries: la maestra redactó consultas LinkedIn específicas por currículum.
- Jobs: JobSpy raspó LinkedIn usando esas consultas, generando ~10,000 postings, todos vinculados a una consulta que la maestra escribió para ese currículum.
- Labels: la maestra puntuó cada par (resume, job) en las cinco dimensiones y añadió una oración de razonamiento por dimensión.
Entrenamiento de destilación:
- Dos corridas LoRA SFT en una A100 via Modal, una por tarea (query generation y fit evaluation).
- Adapter: rank 16, alpha 16, dropout off, con proyecciones en atención y MLP.
- Schedule: una epoch por tarea, checkpoints cada 200 steps para sanity checks.
- Output: safetensors en
build-small-hackathon/job-searcher-qwen3-8By una versión Q4_K_M + LoRA-GGUF sidecars parallama.cppenbuild-small-hackathon/job-searcher-qwen3-8B-gguf.
La configuración LoRA usada se resume así: LoraConfig(r=16, lora_alpha=16, task_type="CAUSAL_LM", target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]).
Despliegue, latencia y experiencia de streaming
El Space corre llama-cpp-python con la rueda CUDA precompilada en un HuggingFace ZeroGPU Space. Dos decisiones de diseño clave mejoran latencia y coste:
-
ZeroGPU recicla el contexto CUDA por llamada, así que no conviene mantener una instancia a nivel de módulo entre invocaciones. El Space hace una llamada GPU por submission (todo un currículum) en vez de una por oferta. Es decir: se carga el modelo una sola vez y se evalúan todas las vacantes del envío en esa única llamada.
-
Streaming: la UI recibe el razonamiento token a token usando una API estilo OpenAI
create_chat_completion(stream=True). Eso permite ver el proceso de razonamiento en vivo, no solo el resultado final.
El demo en vivo y los artefactos están publicados: el Space público y un dataset de traces de la sesión de Claude Code que reconstruye cada evento (raw JSONL), ideal si quieres estudiar cómo se resolvieron errores y caminos muertos durante la construcción.
Lecciones prácticas y decisiones que importan
-
Dos adaptadores superaron a uno. Intenté unir generación de queries y evaluación en un solo LoRA y el modelo se "filtraba" formatos (JSON en queries y prosa en evaluación). Separar las tareas en dos cabezas en el mismo backbone, intercambiadas por llamada, eliminó esa clase de bugs.
-
El prompt de la maestra importó más que el tamaño de la estudiante. Refinar la guía de etiquetado para puntuar contra detalles concretos del currículum (por ejemplo: "cuatro años de Rust; el rol pide cinco") propagó ese rigor durante la distilación. La estudiante aprendió a ser específica en sus justificaciones.
-
Diseñar para coste y UX: cuantizar a
Q4_K_M, usar LoRA sidecars GGUF y agrupar evaluaciones por envío reduce costos y latencia en un entorno con recursos limitados.
Si quieres un sistema que te explique por qué una oferta te conviene o no, no basta con fine-tuning: necesitas etiquetas con criterio, un teacher que sea consistente y un despliegue que no repita cargas inútiles.
Este proyecto no es la panacea, pero es un buen ejemplo de cómo combinar modelos grandes, distilación estructurada y decisiones de ingeniería para convertir una tarea cotidiana y tediosa en un proceso automático, transparente y defendible. ¿Listo para dejar de filtrar 50 ofertas y empezar a leer razones precisas?
Fuente original
https://huggingface.co/blog/build-small-hackathon/job-search-blog
