Spaces:
Sleeping
Sleeping
| # 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} |