Contexte Professionnel et Solution IA
Le volume croissant de candidatures dans les départements des Ressources Humaines pose un défi logistique majeur. Le tri manuel des Curriculum Vitae (CV) par rapport aux Descriptions de Poste (DP) est souvent源 à la fatigue cognitive et à l'oubli de profils pertinents. L'intégration de modèles de langage de grande taille (LLM) comme Qwen3.5-9B permet d'automatiser cette tâche critique. Grâce à sa capacité de traitement de contexte long et son raisonnement logique avancé, ce modèle peut structurer des données non structurées en informations exploitables.
Valeurs ajoutées immédiates :
- Découpage sémantique automatique des exigences fonctionnelles.
- Evaluation objective des compétances transférables.
- Génération instantanée de métriques de compatibilité.
- Standardisation des processus de pré-sélection.
Configuration de l'Environnement Technique
1. Infrastructure Logicielle
# Initialisation de l'espace isolé
conda create --name hr-assistant python=3.11
conda activate hr-assistant
# Installation des bibliothèques critiques
pip install torch==2.4.0 huggingface_hub>=0.23.0 transformers>=4.40.0 gradio>=4.22.0 jinja2
2. Organisation du Répertoire
/proj_rh_qwen/
├── src/
│ ├── __init__.py
│ ├── extractor.py # Module d'extraction DP
│ └── evaluator.py # Module de scoring
├── ui_interface.py # Point d'entrée Graphique
├── config.yaml # Paramétrage système
└── static/
└── styles.css # Mise en page rapport
Implémentation des Algorithmes
Module d'Extraction d'Informations
Cette étape transforme le texte brut de l'offre en JSON structuré. Nous utilisons un prompt engineering spécifique pour garantir la cohérence des retours.
import json
from typing import Dict, List
class ExigenceOffre:
def __init__(self):
self.extraiteur = None
def analyser_demande(self, texte_offre: str) -> Dict[str, any]:
"""Transforme la description de poste en données structurées."""
message_systeme = """Agir en tant qu'analyste RH expert. Extrayez strictement :
1. Les compétences obligatoires (liste strings)
2. Les compétences souhaitées (liste strings)
3. Niveau d'expérience requis (int)
4. Niveau d'études attendu (string)"""
réponse_generée = self.extraieteur.generate(
role_user=message_systeme,
utilisateur=texte_offre
)
return json.loads(réponse_generée)
def extraire_exigences(offre_texte: str) -> dict:
schema_resultat = {
"competences_critiques": [],
"competences_socle": [],
"annee_expérience": "",
"diplome_minimal": ""
}
demande_prompt = f"""
Analyse suivante offre : '{offre_texte}'
Retourne un JSON unique contenant les clés définies dans le schéma ci-dessus.
Ne laisse aucun commentaire markdown.
"""
sortie_llm = modèl_qwen.generate(demande_prompt)
return sortie_llm
Module d'Evaluation de Compatibilité
Une fois les exigences connues, le modèle compare le contenu du CV candidat avec ces exigences pour fournir un score pondéré.
def calculer_cohérence_cv(exigences: dict, cv_contenu: str) -> dict:
"""Calcule le pourcentage d'adéquation entre profil et poste."""
contexte_evaluation = f"""
CONTEXTE POSTE: {json.dumps(exigences)}
CANDIDAT:
{cv_contenu}
TACHES:
- Noter chaque compétence (0 à 5 points)
- Synthétiser l'écart expérience/diplôme
- Calculer score global pondéré (sur 100)
"""
retour_analyse = modèl_qwen.generate(contexte_evaluation)
try:
resultat_json = json.loads(retour_analyse)
return {
'score_final': resultat_json.get('score', 0),
'details_competences': resultat_json.get('mapping', []),
'justification': 'Analyse complète générée'
}
except Exception as e:
raise ValueError(f"Parsing échoué : {str(e)}")
Déploeiment de l'Interface Homme-Machine
Création du Tableau de Bord
L'interface web facilite la saisie des textes et la visualisation des résultats sans nécessiter de connaissances techniques approfondies.
import gradio as gr
def interface_utilisateur():
titre_accueil = "**Assistant de Pré-Sélection Automatisée**"
with gr.Blocks() as application:
gr.Markdown(titre_accueil)
with gr.Row():
with gr.Column(scale=1):
zone_offre = gr.Textbox(label="Collez la Description de Poste ici", lines=10)
bouton_analyser = gr.Button("Traiter l'Offre", variant="primary")
with gr.Column(scale=1):
zone_cv = gr.Textbox(label="Collez le Curriculum Vitae ici", lines=10)
bouton_scoring = gr.Button("Comparer au CV", variant="secondary")
output_jd = gr.JSON(label="Structure de l'Offre Extraite", visible=False)
output_score = gr.JSON(label="Rapport de Matching")
boutons = [bouton_analyser, bouton_scoring]
bouton_analyser.click(
fn=extraire_exigences,
inputs=zone_offre,
outputs=output_jd
).then(
lambda x: gr.update(visible=True, value=x),
inputs=output_jd,
outputs=[output_jd]
)
bouton_scoring.click(
fn=lambda jd_resu, cv_text: calculer_cohérence_cv(jd_resu, cv_text),
inputs=[output_jd, zone_cv],
outputs=output_score
)
if __name__ == "__main__":
interface_utilisateur().launch(server_name="0.0.0.0", share=False)
Cas de Validation Fonctionnelle
Scénario Développeur Backend
L'entrée d'une requête technique type produit une extraction précise :
{
"competences_critiques": ["Python", "API REST", "Docker"],
"competences_socle": ["Git", "SQL"],
"annee_expérience": "2+ ans",
"diplome_minimal": "Master Informatique ou équivalent"
}
Comparé à un profil candidat disposant de 3 ans d'expérience Python et d'une formation d'ingénieur, le système génère un score de 88/100, notant une légère divergence sur Docker qu'il classe en apprentissage en cours.
Méthodes de Robustesse et Performance
Chargement Optimisé du Modèle
Pour réduire la latence lors de l'exécution locale ou sur serveur VPC, l'utilisation de la quantisation 4-bit est recommandée.
from transformers import AutoTokenizer, AutoModelForCausalLM
config_tuning = {
"device_map": "auto",
"torch_dtype": "float16",
"load_in_4bit": True
}
toko_tokeniseur = AutoTokenizer.from_pretrained("Qwen/Qwen3.5-9B")
model_reduit = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-9B", **config_tuning)
Chaîne de Vérification Finale
Avant d'afficher le score à l'RH, une étape de révision par le modèle elle-même permet de détecter les contradictions flagrantes.
def verifier_coherence(offre_scie, score_brut):
instruction_check = f"""
Vérifie si le résultat suivant est réaliste au vu des exigences :
Exigence: {json.dumps(offre_scie)}
Résultat: {score_brut}
Si le score semble injustifié (trop haut ou trop bas), indiquez pourquoi.
"""
correction = model.generate(instruction_check)
return correction