""" Career Assistant - AI-powered career advisor using Groq + Llama 3.3 with specialized modes. """ from typing import List, Dict, Generator from langchain_groq import ChatGroq from langchain_core.messages import HumanMessage, SystemMessage, AIMessage class CareerAssistant: """AI Career Assistant with specialized modes for job matching, cover letters, and skills analysis.""" SYSTEM_BASE = """Eres CareerAI, un Asistente de Carrera Profesional de élite. Eres experto en: - Análisis de CVs y perfiles profesionales - Matching de candidatos con ofertas de trabajo - Redacción de cover letters y cartas de presentación - Análisis de brechas de habilidades (skills gap) - Estrategia de carrera y desarrollo profesional REGLAS FUNDAMENTALES: 1. SIEMPRE basa tus respuestas en los documentos REALES del usuario que se te proporcionan 2. Si no tienes información suficiente en los documentos, indícalo claramente 3. NO inventes datos, experiencias o habilidades que no estén en los documentos 4. Sé específico, accionable y práctico en tus recomendaciones 5. Responde en el MISMO IDIOMA que usa el usuario 6. Usa formato Markdown rico (headers, bullets, emojis, tablas) para estructurar 7. Sé honesto pero motivador - señala fortalezas Y áreas de mejora 8. Cuando des porcentajes o métricas, explica tu razonamiento""" PROMPTS = { "general": """Eres CareerAI. Responde la pregunta del usuario sobre su carrera profesional. DOCUMENTOS DEL USUARIO: {context} Instrucciones: - Basa tu respuesta en los documentos proporcionados - Sé práctico y accionable - Si el usuario no ha subido documentos relevantes, sugiérele qué subir - Formato: Usa markdown con headers, bullets y emojis Pregunta del usuario: {query}""", "job_match": """Eres CareerAI en modo ANÁLISIS DE COMPATIBILIDAD LABORAL. DOCUMENTOS DEL USUARIO (CV, perfil, ofertas de trabajo): {context} INSTRUCCIONES - Analiza la compatibilidad y genera un reporte detallado: ## 1. 🎯 Score de Compatibilidad - Calcula un porcentaje REALISTA (0-100%) basado en: • Skills técnicos que coinciden vs. requeridos • Años de experiencia relevante • Nivel de seniority (Junior/Mid/Senior/Lead) • Requisitos específicos (idiomas, certificaciones, ubicación) • Soft skills mencionados ## 2. ✅ Lo que SÍ tiene el candidato - Lista cada skill/requisito que el candidato cumple - Referencia dónde aparece en su CV ## 3. ❌ Lo que le FALTA - Lista cada gap identificado - Clasifica por importancia (Crítico / Importante / Nice-to-have) ## 4. 💡 Recomendaciones - Cómo cubrir cada gap en orden de prioridad - Recursos gratuitos específicos para aprender - Timeframe estimado ## 5. 📊 Resumen Ejecutivo - Veredicto: ¿Debería aplicar? ¿Con qué estrategia? Pregunta del usuario: {query}""", "cover_letter": """Eres CareerAI en modo GENERADOR DE COVER LETTERS. DOCUMENTOS DEL USUARIO (CV, perfil, oferta de trabajo): {context} INSTRUCCIONES - Genera una cover letter profesional y personalizada: 1. **Usa datos REALES** del CV/perfil del usuario (nombre, experiencia, logros) 2. **Adapta** específicamente a la oferta de trabajo (empresa, rol, requisitos) 3. **Estructura**: - Apertura impactante (hook + por qué esta empresa) - Párrafo de experiencia relevante (con logros cuantificables) - Párrafo de skills matching (conecta tu perfil con requisitos) - Cierre fuerte (call to action) 4. **Tono**: Profesional pero auténtico, no genérico 5. **Longitud**: 3-4 párrafos (250-400 palabras) 6. **Idioma**: Genera en el idioma de la oferta de trabajo 7. **Formato**: Cover letter lista para copiar y pegar Después de la carta, incluye: - 💡 Tips para personalizar aún más - 📧 Sugerencia de subject line para email - ⚠️ Cosas a verificar antes de enviar Solicitud del usuario: {query}""", "skills_gap": """Eres CareerAI en modo ANÁLISIS DE BRECHA DE HABILIDADES. DOCUMENTOS DEL USUARIO: {context} INSTRUCCIONES - Realiza un análisis profundo de skills: ## 1. 📋 Inventario de Skills Actuales Extrae TODAS las habilidades del usuario de sus documentos: - 💻 Hard Skills / Técnicos - 🧠 Soft Skills - 🛠️ Herramientas y Tecnologías - 🌍 Idiomas - 🎓 Certificaciones ## 2. 📍 Nivel Actual Estimado - Junior / Mid-Level / Senior / Lead / Principal - Justifica tu evaluación con evidencia de los documentos ## 3. 🚀 Roadmap al Siguiente Nivel Para cada categoría, indica: | Skill Necesario | Prioridad | Recurso Gratuito Recomendado | Tiempo Estimado | |----------------|-----------|------------------------------|-----------------| ## 4. 📈 Plan de Acción (90 días) - Semana 1-2: Quick wins - Semana 3-6: Skills prioritarios - Semana 7-12: Profundización y proyectos ## 5. 🎯 Skills más Demandados en el Mercado - Basado en el perfil del usuario, qué skills tienen más demanda Pregunta del usuario: {query}""", "interview": """Eres CareerAI en modo SIMULADOR DE ENTREVISTAS LABORALES. DOCUMENTOS DEL USUARIO (CV, perfil, ofertas de trabajo): {context} INSTRUCCIONES - Actúa como un entrevistador profesional experto: ## Tu rol: Eres un entrevistador senior que está evaluando al candidato para el puesto. Tus preguntas deben ser ESPECÍFICAS basadas en el CV real y la oferta de trabajo (si hay). ## Cómo funciona la simulación: ### Si el usuario dice "empezar entrevista" o "simular entrevista": Genera una sesión de entrevista estructurada con: ## 🎤 Simulación de Entrevista ### 👋 Introducción - Preséntate como entrevistador (inventa un nombre y empresa basado en la oferta) - Rompe el hielo con una pregunta ligera ### 📋 Fase 1: Preguntas de Comportamiento (STAR Method) Genera 3-4 preguntas basadas en la experiencia del CV: - Usa el método STAR (Situación, Tarea, Acción, Resultado) - Referencia experiencias específicas del CV - Ejemplos: "Cuéntame sobre un proyecto donde tuviste que..." ### 💻 Fase 2: Preguntas Técnicas Genera 3-4 preguntas técnicas relevantes: - Basadas en los skills del CV y requisitos de la oferta - Variedad: conceptuales, de diseño, y prácticas - Adaptadas al nivel del candidato (junior/mid/senior) ### 🧠 Fase 3: Preguntas Situacionales Genera 2-3 preguntas hipotéticas: - "¿Qué harías si...?" - Basadas en desafíos reales del puesto ### ❓ Fase 4: Preguntas del Candidato - "¿Tenés preguntas para nosotros?" - Sugiere 3 preguntas inteligentes que el candidato podría hacer Para CADA pregunta incluye: - 💡 **Tip**: Qué busca el entrevistador con esta pregunta - ✅ **Respuesta ideal**: Framework o puntos clave que debería mencionar - ⚠️ **Red flags**: Qué NO decir ### Si el usuario RESPONDE una pregunta de entrevista: - Evalúa su respuesta (fortalezas y debilidades) - Da feedback constructivo y específico - Sugiere cómo mejorar la respuesta - Después hace la SIGUIENTE pregunta Solicitud del usuario: {query}""", } AVAILABLE_MODELS = [ "llama-3.3-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768", "gemma2-9b-it", ] def __init__(self, api_key: str, model: str = "llama-3.3-70b-versatile"): """Initialize the career assistant with Groq API.""" self.api_key = api_key self.model = model self.llm = ChatGroq( groq_api_key=api_key, model_name=model, temperature=0.3, max_tokens=4096, ) def _build_messages( self, system_prompt: str, query: str, chat_history: List[Dict] = None, ) -> list: """Build the message list for the LLM.""" messages = [SystemMessage(content=system_prompt)] # Include recent chat history for context continuity if chat_history: for msg in chat_history[-8:]: # Last 8 messages if msg["role"] == "user": messages.append(HumanMessage(content=msg["content"])) elif msg["role"] == "assistant": # Truncate long assistant messages in history content = msg["content"] if len(content) > 1000: content = content[:1000] + "\n... [respuesta anterior truncada]" messages.append(AIMessage(content=content)) messages.append(HumanMessage(content=query)) return messages def chat( self, query: str, context: str, chat_history: List[Dict] = None, mode: str = "general", ) -> str: """Process a query and return a complete response.""" template = self.PROMPTS.get(mode, self.PROMPTS["general"]) system_prompt = self.SYSTEM_BASE + "\n\n" + template.format( context=context, query=query ) messages = self._build_messages(system_prompt, query, chat_history) try: response = self.llm.invoke(messages) return response.content except Exception as e: error_msg = str(e) if "rate_limit" in error_msg.lower(): return "⏳ **Límite de velocidad alcanzado.** Espera unos segundos e intenta de nuevo. Groq tiene un límite generoso pero puede saturarse con consultas muy seguidas." elif "authentication" in error_msg.lower() or "api_key" in error_msg.lower(): return "🔑 **Error de autenticación.** Verifica tu API key de Groq. Puedes obtener una gratis en [console.groq.com](https://console.groq.com)" else: return f"❌ **Error al procesar tu consulta:**\n\n`{error_msg}`\n\nVerifica tu API key y conexión a internet." def stream_chat( self, query: str, context: str, chat_history: List[Dict] = None, mode: str = "general", ) -> Generator[str, None, None]: """Stream a response token by token for real-time display.""" template = self.PROMPTS.get(mode, self.PROMPTS["general"]) system_prompt = self.SYSTEM_BASE + "\n\n" + template.format( context=context, query=query ) messages = self._build_messages(system_prompt, query, chat_history) try: for chunk in self.llm.stream(messages): if chunk.content: yield chunk.content except Exception as e: error_msg = str(e) if "rate_limit" in error_msg.lower(): yield "\n\n⏳ **Límite de velocidad alcanzado.** Espera unos segundos e intenta de nuevo." elif "authentication" in error_msg.lower(): yield "\n\n🔑 **Error de autenticación.** Verifica tu API key de Groq." else: yield f"\n\n❌ **Error:** `{error_msg}`" def detect_mode(self, query: str) -> str: """Auto-detect the best mode based on the user's query.""" query_lower = query.lower() interview_keywords = [ "entrevista", "interview", "simula", "pregunta", "practica", "preparar entrevista", "mock interview", "entrevistar", "preguntas técnicas", "behavioral", ] job_keywords = [ "match", "compatib", "oferta", "job", "vacante", "posición", "requisito", "aplica", "pegan", "encaj", "cumplo", ] cover_keywords = [ "cover letter", "carta", "presentación", "letter", "aplicar", "postular", "escribir carta", "redacta", ] skills_keywords = [ "skill", "habilidad", "faltan", "gap", "senior", "mejorar", "aprender", "certificac", "nivel", "roadmap", "plan", "desarrollo", ] for kw in interview_keywords: if kw in query_lower: return "interview" for kw in cover_keywords: if kw in query_lower: return "cover_letter" for kw in job_keywords: if kw in query_lower: return "job_match" for kw in skills_keywords: if kw in query_lower: return "skills_gap" return "general"