# Mise à jour pour src/services/interview_service.py import os from typing import Dict, List, Any from typing_extensions import TypedDict from langchain_core.messages import AIMessage, SystemMessage, HumanMessage from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langchain_openai import ChatOpenAI from src.config import read_system_prompt, format_cv class State(TypedDict): messages: List[add_messages] class EnhancedInterviewService: def __init__(self, models: Dict[str, Any]): self.models = models self.llm = self._get_llm() self.system_prompt_template = self._load_prompt_template() self.graph = self._build_graph() def _get_llm(self) -> ChatOpenAI: openai_api_key = os.getenv("OPENAI_API_KEY") return ChatOpenAI( temperature=0.6, model_name="gpt-4o-mini", api_key=openai_api_key ) def _load_prompt_template(self) -> str: return read_system_prompt('prompts/enhanced_rag_prompt.txt') def _analyze_candidate_profile(self, cv_data: Dict[str, Any]) -> Dict[str, str]: """Analyse le profil candidat pour générer des insights pour l'entretien""" # Analyse des compétences avec niveaux skills_analysis = self._generate_skills_analysis(cv_data) # Analyse de reconversion reconversion_analysis = self._generate_reconversion_analysis(cv_data) return { "skills_analysis": skills_analysis, "reconversion_analysis": reconversion_analysis } def _generate_skills_analysis(self, cv_data: Dict[str, Any]) -> str: """Génère une analyse textuelle des compétences pour le prompt""" competences = cv_data.get("analyse_competences", []) if not competences: return "Aucune analyse de compétences disponible." # Grouper par niveau levels_groups = { "expert": [], "avance": [], "intermediaire": [], "debutant": [] } for comp in competences: level = comp.get("level", "debutant") skill = comp.get("skill", "") if skill and level in levels_groups: levels_groups[level].append(skill) # Construire l'analyse textuelle analysis_parts = [] if levels_groups["expert"]: analysis_parts.append(f"COMPÉTENCES EXPERTES : {', '.join(levels_groups['expert'])}") analysis_parts.append("→ Pose des questions techniques approfondies, demande des exemples d'innovation et de leadership technique") if levels_groups["avance"]: analysis_parts.append(f"COMPÉTENCES AVANCÉES : {', '.join(levels_groups['avance'])}") analysis_parts.append("→ Explore les défis complexes, l'autonomie et la résolution de problèmes") if levels_groups["intermediaire"]: analysis_parts.append(f"COMPÉTENCES INTERMÉDIAIRES : {', '.join(levels_groups['intermediaire'])}") analysis_parts.append("→ Vérifie la compréhension pratique avec des exemples concrets") if levels_groups["debutant"]: analysis_parts.append(f"COMPÉTENCES DÉBUTANTES : {', '.join(levels_groups['debutant'])}") analysis_parts.append("→ Teste les connaissances de base et évalue la motivation à apprendre") return "\n".join(analysis_parts) if analysis_parts else "Aucune compétence analysée." def _generate_reconversion_analysis(self, cv_data: Dict[str, Any]) -> str: """Génère une analyse de reconversion pour le prompt""" reconversion_data = cv_data.get("reconversion", {}) if not reconversion_data: return "Aucune analyse de reconversion disponible." is_reconversion = reconversion_data.get("is_reconversion", False) analysis = reconversion_data.get("analysis", "") if not is_reconversion: return "PROFIL CLASSIQUE : Parcours cohérent dans le domaine. Focus sur l'évolution et les projets marquants." reconversion_guidance = [ "CANDIDAT EN RECONVERSION DÉTECTÉE :", f"Analyse : {analysis}", "", "POINTS À EXPLORER OBLIGATOIREMENT :", "1. Motivations du changement de carrière", "2. Compétences transférables de l'expérience passée", "3. Démarches d'apprentissage et de formation", "4. Engagement et projets dans la nouvelle voie", "5. Vision à long terme dans ce nouveau domaine", "", "APPROCHE : Valorise l'expérience passée, rassure sur la pertinence de la reconversion" ] return "\n".join(reconversion_guidance) def _chatbot_node(self, state: State) -> Dict[str, Any]: messages = state["messages"] formatted_cv_str = format_cv(self.cv_data) # Générer les analyses du profil candidat profile_analysis = self._analyze_candidate_profile(self.cv_data) # Formatage du prompt système enrichi system_prompt = self.system_prompt_template.format( entreprise=self.job_offer.get('entreprise', 'notre entreprise'), poste=self.job_offer.get('poste', 'ce poste'), mission=self.job_offer.get('mission', 'Non spécifiée'), profil_recherche=self.job_offer.get('profil_recherche', 'Non spécifié'), competences=self.job_offer.get('competences', 'Non spécifiées'), pole=self.job_offer.get('pole', 'Non spécifié'), cv=formatted_cv_str, skills_analysis=profile_analysis["skills_analysis"], reconversion_analysis=profile_analysis["reconversion_analysis"] ) llm_messages = [SystemMessage(content=system_prompt)] + messages response = self.llm.invoke(llm_messages) return {"messages": [response]} def _build_graph(self) -> any: graph_builder = StateGraph(State) graph_builder.add_node("chatbot", self._chatbot_node) graph_builder.add_edge(START, "chatbot") graph_builder.add_edge("chatbot", END) return graph_builder.compile() def process_conversation( self, cv_document: Dict[str, Any], job_offer: Dict[str, Any], conversation_history: List[Dict[str, Any]], messages: List[Dict[str, Any]] ) -> Dict[str, Any]: if not cv_document or 'candidat' not in cv_document: raise ValueError("Document CV invalide fourni.") if not job_offer: raise ValueError("Données de l'offre d'emploi non fournies.") self.job_offer = job_offer self.cv_data = cv_document['candidat'] self.conversation_history = conversation_history initial_state = conversation_history + messages result = self.graph.invoke({"messages": initial_state}) response_content = result["messages"][-1].content return {"response": response_content}