Système Intelligent de Recrutement : Analyse d'Offres et Evaluation de Candidats sous Qwen3.5-9B

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

Étiquettes: Qwen3.5 Gradio Python nlp Recrutement

Publié le 29 septembre à 13h09