|
- using Reranker;
- using System.Text;
- using ToolsServices;
-
- namespace Services
- {
-
- public static class RAGService
- {
- #region Variables
- private static Services.ReActAgent.ReActAgent _Agent = new();
- private static string NomFichierData = FichiersInternesService.ParamsRAG;//"paramsRAG.txt";
- #endregion
-
- #region Méthodes publiques
-
- #region Gestion des paramètres
- public static ParametresRAGService LoadParametres()
- {
- LoggerService.LogInfo("RAGService.LoadParametres");
-
- ParametresRAGService SelectedItem = new();
-
- try
- {
- if (File.Exists(NomFichierData))
- {
-
- string[] lignes = File.ReadAllLines(NomFichierData);
-
- if (lignes.Length > 0)
- SelectedItem.RAG_Path_ToVectoralize = lignes[0];
-
- if (lignes.Length > 1)
- SelectedItem.RAG_Path_Vectoralized = lignes[1];
-
- }
- return SelectedItem;
-
- }
- catch
- {
- return new();
- }
- }
-
- public static bool SaveParametres(ParametresRAGService selectedItem)
- {
- LoggerService.LogInfo("RAGService.SaveParametres");
-
- try
- {
- StringBuilder sb = new();
- sb.AppendLine(selectedItem.RAG_Path_ToVectoralize);
- sb.AppendLine(selectedItem.RAG_Path_Vectoralized);
-
- File.WriteAllText(NomFichierData, sb.ToString());
-
- return true;
- }
- catch
- {
- return false;
- }
-
- }
- #endregion
-
- private static async Task ChargementDocuments(QdrantService qdrant, bool isRAZCollections)
- {
- LoggerService.LogInfo("RAGService.ChargementDocuments");
-
- ParametresRAGService? ragParam = LoadParametres();
- // Chargement documents
- await IngestDocuments(ragParam.RAG_Path_ToVectoralize, ragParam.RAG_Path_Vectoralized, qdrant, isRAZCollections);
-
- }
-
- /*
- public static async Task<(bool, string, string)> RechercheDansDocuments(string query, string model)
- {
- LoggerService.LogInfo("RAGService.RechercheDansDocuments");
-
- await ChargementDocuments();
-
- // Cela inclut pour quey : le split en chunck + tokenisation + vectorisation
- LoggerService.LogDebug("Debut de RAGService.RechercheDansDocuments");
- var (reponse, m) = await _Agent.RechercheRAG(query, "Recherche RAG", model);
- LoggerService.LogDebug("Fin de RAGService.RechercheDansDocuments");
- return (true, reponse, m);
- }
- */
- public static async Task<string> SaveDocumentAsync(byte[] data, string fileName, Domain domain)
- {
- ParametresRAGService? ragParam = LoadParametres();
- // Sécurisation du filename pour éviter ../ etc.
- var safeName = Path.GetFileName(fileName);
-
- // Chemin final
- var fullPath = Path.Combine(ragParam.RAG_Path_ToVectoralize, domain.ToCollectionName(), safeName);
-
- // Écriture du fichier
- await File.WriteAllBytesAsync(fullPath, data);
-
- return fullPath;
- }
- /// <summary>
- /// Recherche dans la base vectorielle Qdrant
- /// </summary>
- /// <param name="query"></param>
- /// <param name="model"></param>
- /// <returns></returns>
- public static async Task<(bool, string, string)> RechercheDansDocuments(Domain domain, string query, string model, bool isRAZCollections)
- {
- LoggerService.LogInfo("RAGService.RechercheDansDocuments");
- var qdrant = new QdrantService();
- await ChargementDocuments(qdrant, isRAZCollections);
-
- // Cela inclut pour quey : le split en chunck + tokenisation + vectorisation
- LoggerService.LogDebug("Debut de RAGService.RechercheDansDocuments");
- (bool b, float[]? queryEmbedding) = await _Agent.GetEmbeddingAsync(query, true);
- if(b && queryEmbedding != null)
- {
- LoggerService.LogDebug("Embedding de la requête OK");
- }
- else
- {
- LoggerService.LogWarning("Problème lors de la vectorisation de la requête");
- return (false, "Problème lors de la vectorisation de la requête", "");
- }
-
- var results = await qdrant.SearchAsync(domain, queryEmbedding, 0.1f, topK: 20);
- /*
- var retrievedChunks = results
- .Select(r => (r.Text + " - source : " + r.Nom_Fichier))
- .ToArray();
- */
-
- // --- Reranking : Charger tokenizer et cross-encoder ONNX
- //var reranker = new BgeReranker();
- var ranked = BgeReranker.Rerank(query, results, topK: 5);
-
- if (ranked.Count() > 0)
- {
- //var (reponse, m) = await _Agent.RechercheRAG(query, "Recheche RAG", model, retrievedChunks);
- var (reponse, m) = await _Agent.RechercheRAG(query, "Recheche RAG", model, ranked, domain.ToCollectionName());
- LoggerService.LogDebug("Fin de RAGService.RechercheDansDocuments avec demande au LLM");
- return (true, reponse, m);
- }
- else
- {
- LoggerService.LogDebug("Fin de RAGService.RechercheDansDocuments sans demande au LLM");
- return (true, "Pas de réponse contenue dans la base vectorielle", "Pas de LLM sollicité");
- }
- }
-
- /// <summary>
- /// Recherche dans les collections CV ou Emails
- /// </summary>
- /// <param name="domain"></param>
- /// <param name="query"></param>
- /// <returns></returns>
- public static async Task<string> Search(Domain domain, string query, string adresseMail="")
- {
- LoggerService.LogInfo("RAGService.Search");
- var qdrant = new QdrantService();
- //await ChargementDocuments(qdrant, false);
-
- // Cela inclut pour quey : le split en chunck + tokenisation + vectorisation
- (bool b, float[]? queryEmbedding) = await _Agent.GetEmbeddingAsync(query, true);
- if (b && queryEmbedding != null)
- {
- LoggerService.LogDebug("Embedding de la requête OK");
- }
- else
- {
- LoggerService.LogWarning("Problème lors de la vectorisation de la requête");
- return ("");
- }
- StringBuilder sb = new();
-
- var results = await qdrant.SearchAsync(domain, queryEmbedding,0.1f, topK: 20, adresseMail: adresseMail);
- /*
- var retrievedChunks = results
- .Select(r => r.Text)
- .ToArray();
- */
- var ranked = BgeReranker.Rerank(query, results, topK: 5);
-
- if (ranked.Count() > 0)
- {
- foreach (var entry in ranked)
- {
- sb.AppendLine($"Document: {entry.Nom_Fichier}");
- /*
- if (entry.Payload.TryGetValue("nom_fichier", out var value2))
- {
- if (value2.StringValue != "")
- {
- sb.AppendLine($"Email: {value2.StringValue}");
- }
- }
- */
- sb.AppendLine("------");
- }
- return (sb.ToString());
- }
- else
- {
- LoggerService.LogDebug("Fin de RAGService.RechercheDansDocuments sans demande au LLM");
- return ("");
- }
- }
-
- // Documents pas forcément RAG donc Domain en fonction du contexte
- public static async Task<bool> IngestDocument(Domain domain,bool isOnDemand, bool isRAZCollections, string content, string filename, string emailObject = "", string proprietaire = "", string niveauAcces="")
- {
- LoggerService.LogInfo("RAGService.IngestDocument");
-
- try
- {
- QdrantService qdrant = new();
- var chunks = ChunkText(content);
-
- await qdrant.InitializeCollectionsAsync(isRAZCollections);
-
- for (int i = 0; i < chunks.Length; i++)
- {
- var chunk = chunks[i];
- (bool b, float[]? embedding) = await _Agent.GetEmbeddingAsync(chunk, isOnDemand);
- if (b && embedding != null)
- {
- var id = $"{Path.GetFileName(filename)}_chunk_{i}";
- await qdrant.IngestDocument(domain, embedding, filename, id, chunk, emailObject, proprietaire, niveauAcces);
- }
- }
- return true;
- }
- catch (Exception ex)
- {
- LoggerService.LogError($"RAGService.IngestDocument : {ex.Message}");
- return false;
- }
- }
-
- public static string[] ChunkText(string text, int chunkSize = 250, int overlap = 50)
- {
- var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
- var chunks = new List<string>();
- /*
- for (int i = 0; i < words.Length; i += (chunkSize - overlap))
- {
- var chunkWords = words.Skip(i).Take(chunkSize);
- chunks.Add(string.Join(' ', chunkWords));
- }
- */
- int start = 0;
-
- while (start < words.Length)
- {
- int length = Math.Min(chunkSize, words.Length - start);
- var chunk = string.Join(' ', words.Skip(start).Take(length));
- chunks.Add(chunk);
- start += chunkSize - overlap;
- }
-
- return chunks.ToArray();
- }
- #endregion
-
- #region Méthodes privées
- // Pour l'instant Documents RAG
- private static async Task<bool> IngestDocuments(string pathDocs, string pathDestinationDocs, QdrantService qdrant, bool isRAZCollections)
- {
- LoggerService.LogInfo("RAGService.IngestDocuments");
-
- // boucler sur les documents et envoyer à _Agent.IngestDocumentAsync
- // Cela inclut le split en chunck + tokenisation + vectorisation + sauvegarde des vecteurs
- try
- {
- if (!System.IO.Directory.Exists(pathDocs))
- {
- LoggerService.LogWarning($"Le dossier n'existe pas : {pathDocs}");
- return false;
- }
-
- await qdrant.InitializeCollectionsAsync(isRAZCollections);
-
-
- List<string> filesToDeplace = new();
- var files = Directory.GetFiles(pathDocs, "*.*", SearchOption.AllDirectories);
- foreach (var file in files)
- {
- var relativePath = System.IO.Path.GetRelativePath(pathDocs, file);
- var content = FilesService.ExtractText(file);
- if (content != string.Empty)
- {
- var domain = DomainExtensions.GetDomainByPath(file);
- // Si ce document y est déjà, le contenu de la base vectorielle sera supprimé avant d'être ingéré de nouveau
- await qdrant.DeleteDocumentInQdrant(domain, Path.GetFileName(file));
-
- var chunks = ChunkText(content);
-
- for (int i = 0; i < chunks.Length; i++)
- {
- var chunk = chunks[i];
- (bool b, float[]? embedding) = await _Agent.GetEmbeddingAsync(chunk, true);
- if (b && embedding != null)
- {
-
-
- var id = $"{Path.GetFileName(file)}_chunk_{i}";
- await qdrant.IngestDocument(domain, embedding, Path.GetFileName(file), id, chunk);
- }
- }
- filesToDeplace.Add(file);
- }
- }
-
- foreach (var file in filesToDeplace)
- {
- // on déplace les fichiers une fois vectorisés
- // Chemin relatif pour recréer les sous-dossiers
- var relativePath = System.IO.Path.GetRelativePath(pathDocs, file);
- var destPath = System.IO.Path.Combine(pathDestinationDocs, relativePath);
-
- // Crée le dossier de destination si nécessaire
- var destDir = System.IO.Path.GetDirectoryName(destPath);
- if (destDir != null && !Directory.Exists(destDir))
- {
- Directory.CreateDirectory(destDir);
- }
-
- // Déplace le fichier
- if(System.IO.File.Exists(file))
- File.Move(file, destPath, overwrite: true);
- }
-
- LoggerService.LogDebug($"Fin de RAGService.IngestDocument : {files.Length} document(s)");
-
- return true;
- }
- catch (Exception ex)
- {
- LoggerService.LogError($"RAGService.IngestDocument : {ex.Message}");
- return false;
- }
- }
-
- #endregion
- }
- }
|