Job Searcher: IA que mejora búsquedas de empleo con razón | Keryc
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-8B y una versión Q4_K_M + LoRA-GGUF sidecars para llama.cpp en build-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?