using ToolsServices; using Services.ReActAgent; using System.Collections.ObjectModel; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; namespace Services { public static class RechercheCVService { #region Variables private static readonly string NomFichierRechercheCV = FichiersInternesService.ListeRechercheCV;// "listeRechercheCV.json"; private static readonly string NomFichierParametres = FichiersInternesService.ParamsRechercheCV; #endregion #region Méthodes publiques public static (string, string) LoadParametres() { LoggerService.LogInfo("RechercheCVService.LoadParametres"); //ParametresOllamaService SelectedItem = new(); try { string FicheMission=""; string PathCV=""; if (File.Exists(NomFichierParametres)) { string[] lignes = File.ReadAllLines(NomFichierParametres); if (lignes.Length > 0) FicheMission = lignes[0]; if (lignes.Length > 1) PathCV = lignes[1]; } return (FicheMission, PathCV); } catch { LoggerService.LogError($"Erreur lors du chargement des paramètres depuis {NomFichierParametres}"); return ("",""); } } public static bool SaveParametres(string FicheMission, string PathCV) { LoggerService.LogInfo("RechercheCVService.SaveParametres"); try { StringBuilder sb = new(); sb.AppendLine(FicheMission); sb.AppendLine(PathCV); File.WriteAllText(NomFichierParametres, sb.ToString()); return true; } catch { LoggerService.LogError($"Erreur lors de la sauvegarde des paramètres dans {NomFichierParametres}"); return false; } } public static async Task?> RechercherCV(string ficheMission, string dossierCV, bool isGenererXML) { LoggerService.LogInfo("RechercheCVService.RechercheCV"); var reActRagAgent = new ReActAgent.ReActAgent(); var modeleIA = ReActAgent.ReActAgent.GetModeleIA(ModelsUseCases.TypeUseCase.AnalyseCVMission); LoggerService.LogInfo($"Analyse des CV"); LoggerService.LogInfo($"Fiche mission : {ficheMission}"); LoggerService.LogInfo($"Dossier CV : {dossierCV}"); LoggerService.LogInfo($"Extraire les CV en XML : {isGenererXML}"); LoggerService.LogInfo($"Modèle utilisé : {modeleIA}"); if (!Directory.Exists(dossierCV)) { LoggerService.LogWarning($"Le dossier {dossierCV} n'existe pas."); return null; } if (!File.Exists(ficheMission)) { LoggerService.LogWarning($"Le fichier {ficheMission} n'existe pas."); return null; } var fichiers = Directory .EnumerateFiles(dossierCV, "*.*", SearchOption.AllDirectories) .Where(f => f.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".docx", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".xlsx", StringComparison.OrdinalIgnoreCase)) .ToArray(); if (fichiers.Length == 0) { LoggerService.LogWarning($"Aucun fichier trouvé dans le dossier spécifié : {dossierCV}"); return null; } string messagePromptInjection1 = ""; bool isPromptInjection = false; var ficheMissionText = reActRagAgent.CleanUserInput(FilesService.ExtractText(ficheMission), out isPromptInjection); if(isPromptInjection) { var msg = "Attention, le texte de la fiche de mission contient des éléments suspects"; messagePromptInjection1 = $"{msg}.\n"; LoggerService.LogWarning(msg); } List retours = new(); foreach (var fichier in fichiers) { var bIngest = await RAGService.IngestDocument(Domain.CV, true, false, FilesService.ExtractText(fichier), fichier); } foreach (var fichier in fichiers) { try { #region Analyse du CV ReponseRechercheCV retour = new(); string messagePromptInjection2 = ""; var texteCV = reActRagAgent.CleanUserInput(FilesService.ExtractText(fichier), out isPromptInjection); if(isPromptInjection) { var msg = "Attention, le texte de la fiche de mission contient des éléments suspects"; messagePromptInjection2 = $"{msg}.\n"; retour.PresenceSuspecte = true; LoggerService.LogWarning($"{msg}: {fichier}"); } /* var prompt = $@" Tu es un expert en recrutement. Ton rôle est d'évaluer la compatibilité entre une offre d'emploi et un CV. Analyse comparative : 1. **Points forts** : liste les éléments du CV qui correspondent bien aux exigences de l'offre (compétences, expériences, formations, etc.). 2. **Points manquants ou faibles** : identifie les éléments de l'offre qui sont absents ou peu développés dans le CV. 3. **Note de compatibilité** : donne une note globale de correspondance entre le CV et l'offre, sur 10, avec une explication. Voici les documents à comparer : --- **Offre d’emploi** : {ficheMissionText} --- **CV du candidat** : {texteCV} --- Réponds en suivant la structure suivante : **Points forts :** - … **Points manquants ou faibles :** - … **Note de compatibilité (sur 10) :** X/10 – Explication : … "; */ var prompt = PromptService.GetPrompt(PromptService.ePrompt.RechercheCVService_RechercheCV_Analyse, ficheMissionText, texteCV); LoggerService.LogInfo($"Analyse du CV : {fichier}"); var (reponse,m1) = await reActRagAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.AnalyseCVMission, true, prompt,"Analyse de CV"); var reponseComplete = $"Pour le CV {fichier} :\n{reponse}\n"; //var reponseClean = reponse.Replace("\\n", "\n").Replace("\\r", "\r"); var reponseCompleteClean = reponseComplete.Replace("\\n", "\n").Replace("\\r", "\r"); retour.FichierCV = fichier; retour.Avis = messagePromptInjection1 + messagePromptInjection2 + reponseCompleteClean; // Note : On peut ajouter une logique pour extraire la note de la réponse si nécessaire if (reponse.Contains("Note de compatibilité")) { var noteMatch = System.Text.RegularExpressions.Regex.Match(reponse, @"(\d+)/10"); if (noteMatch.Success && int.TryParse(noteMatch.Groups[1].Value, out int note)) { retour.Note = note; } } #endregion #region Renseigner le modèle IA retour.ModeleIA = modeleIA; #endregion #region Génération XML if (isGenererXML) { /* var promptXML = $@" Tu es un assistant spécialisé dans l'extraction d'informations depuis des CV. Je vais te fournir le contenu brut d’un CV (texte brut sans mise en forme). À partir de ce contenu, génère un document structuré au format XML en respectant la structure suivante : ... ... ... ... ... Si une information est absente, laisse le champ vide. Ne fais aucun commentaire, retourne uniquement le XML. N'oublie aucune expérience, aucune formation, aucune langue, ni aucune certification. Formate le tout proprement au format XML. N'ajoute pas de texte explicatif. Commence directement par . Voici un contenu PDF extrait : {texteCV} "; */ var promptXML = PromptService.GetPrompt(PromptService.ePrompt.RechercheCVService_RechercheCV_Generate, texteCV); var (reponseXML,m2) = await reActRagAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.AnalyseCVMission, true, promptXML,"CV en XML"); var reponseCompleteCleanXML = reponseXML.Replace("\\n", "\n").Replace("\\r", "\r"); retour.VersionXML = reponseCompleteCleanXML; } #endregion #region Vectorisation du CV await RAGService.IngestDocument(Domain.CV,true, false, texteCV, fichier); //reActRagAgent.IngestDocument(texteCV, fichier); #endregion retours.Add(retour); } catch (Exception ex) { LoggerService.LogError($"Erreur lors de l'extraction du texte de {fichier} : {ex.Message}"); } } LoggerService.LogInfo($"Analyse des CV terminée"); return retours; } public static async Task SauvegarderRechercheAsync(ObservableCollection ListeItems) { LoggerService.LogInfo("RechercheCVService.SauvegarderRechercheAsync"); if (ListeItems == null) return; var options = new JsonSerializerOptions { WriteIndented = true, // Ignore les propriétés non sérialisables comme UniqueId si nécessaire IgnoreReadOnlyProperties = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; using FileStream fs = File.Create(NomFichierRechercheCV); await JsonSerializer.SerializeAsync(fs, ListeItems, options); } public static async Task> ChargerCVDepuisJsonAsync(ObservableCollection ListeItems) { LoggerService.LogInfo("RechercheCVService.ChargerCVDepuisJsonAsync"); if (!File.Exists(NomFichierRechercheCV)) return new ObservableCollection(); using FileStream fs = File.OpenRead(NomFichierRechercheCV); var items = await JsonSerializer.DeserializeAsync>(fs); if (items != null) { ListeItems = items; } return ListeItems; } public static async Task SeekByKeyWord(string RechercheString) { LoggerService.LogInfo("RechercheCVService.SeekByKeyWord"); return await RAGService.Search(Domain.CV, RechercheString); } #endregion #region Méthodes privées #endregion } }