Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

353 Zeilen
13KB

  1. using Reranker;
  2. using System.Text;
  3. using ToolsServices;
  4. namespace Services
  5. {
  6. public static class RAGService
  7. {
  8. #region Variables
  9. private static Services.ReActAgent.ReActAgent _Agent = new();
  10. private static string NomFichierData = FichiersInternesService.ParamsRAG;//"paramsRAG.txt";
  11. #endregion
  12. #region Méthodes publiques
  13. #region Gestion des paramètres
  14. public static ParametresRAGService LoadParametres()
  15. {
  16. LoggerService.LogInfo("RAGService.LoadParametres");
  17. ParametresRAGService SelectedItem = new();
  18. try
  19. {
  20. if (File.Exists(NomFichierData))
  21. {
  22. string[] lignes = File.ReadAllLines(NomFichierData);
  23. if (lignes.Length > 0)
  24. SelectedItem.RAG_Path_ToVectoralize = lignes[0];
  25. if (lignes.Length > 1)
  26. SelectedItem.RAG_Path_Vectoralized = lignes[1];
  27. }
  28. return SelectedItem;
  29. }
  30. catch
  31. {
  32. return new();
  33. }
  34. }
  35. public static bool SaveParametres(ParametresRAGService selectedItem)
  36. {
  37. LoggerService.LogInfo("RAGService.SaveParametres");
  38. try
  39. {
  40. StringBuilder sb = new();
  41. sb.AppendLine(selectedItem.RAG_Path_ToVectoralize);
  42. sb.AppendLine(selectedItem.RAG_Path_Vectoralized);
  43. File.WriteAllText(NomFichierData, sb.ToString());
  44. return true;
  45. }
  46. catch
  47. {
  48. return false;
  49. }
  50. }
  51. #endregion
  52. private static async Task ChargementDocuments(QdrantService qdrant, bool isRAZCollections)
  53. {
  54. LoggerService.LogInfo("RAGService.ChargementDocuments");
  55. ParametresRAGService? ragParam = LoadParametres();
  56. // Chargement documents
  57. await IngestDocuments(ragParam.RAG_Path_ToVectoralize, ragParam.RAG_Path_Vectoralized, qdrant, isRAZCollections);
  58. }
  59. /*
  60. public static async Task<(bool, string, string)> RechercheDansDocuments(string query, string model)
  61. {
  62. LoggerService.LogInfo("RAGService.RechercheDansDocuments");
  63. await ChargementDocuments();
  64. // Cela inclut pour quey : le split en chunck + tokenisation + vectorisation
  65. LoggerService.LogDebug("Debut de RAGService.RechercheDansDocuments");
  66. var (reponse, m) = await _Agent.RechercheRAG(query, "Recherche RAG", model);
  67. LoggerService.LogDebug("Fin de RAGService.RechercheDansDocuments");
  68. return (true, reponse, m);
  69. }
  70. */
  71. public static async Task<string> SaveDocumentAsync(byte[] data, string fileName, Domain domain)
  72. {
  73. ParametresRAGService? ragParam = LoadParametres();
  74. // Sécurisation du filename pour éviter ../ etc.
  75. var safeName = Path.GetFileName(fileName);
  76. // Chemin final
  77. var fullPath = Path.Combine(ragParam.RAG_Path_ToVectoralize, domain.ToCollectionName(), safeName);
  78. // Écriture du fichier
  79. await File.WriteAllBytesAsync(fullPath, data);
  80. return fullPath;
  81. }
  82. /// <summary>
  83. /// Recherche dans la base vectorielle Qdrant
  84. /// </summary>
  85. /// <param name="query"></param>
  86. /// <param name="model"></param>
  87. /// <returns></returns>
  88. public static async Task<(bool, string, string)> RechercheDansDocuments(Domain domain, string query, string model, bool isRAZCollections)
  89. {
  90. LoggerService.LogInfo("RAGService.RechercheDansDocuments");
  91. var qdrant = new QdrantService();
  92. await ChargementDocuments(qdrant, isRAZCollections);
  93. // Cela inclut pour quey : le split en chunck + tokenisation + vectorisation
  94. LoggerService.LogDebug("Debut de RAGService.RechercheDansDocuments");
  95. (bool b, float[]? queryEmbedding) = await _Agent.GetEmbeddingAsync(query, true);
  96. if(b && queryEmbedding != null)
  97. {
  98. LoggerService.LogDebug("Embedding de la requête OK");
  99. }
  100. else
  101. {
  102. LoggerService.LogWarning("Problème lors de la vectorisation de la requête");
  103. return (false, "Problème lors de la vectorisation de la requête", "");
  104. }
  105. var results = await qdrant.SearchAsync(domain, queryEmbedding, 0.1f, topK: 20);
  106. /*
  107. var retrievedChunks = results
  108. .Select(r => (r.Text + " - source : " + r.Nom_Fichier))
  109. .ToArray();
  110. */
  111. // --- Reranking : Charger tokenizer et cross-encoder ONNX
  112. //var reranker = new BgeReranker();
  113. var ranked = BgeReranker.Rerank(query, results, topK: 5);
  114. if (ranked.Count() > 0)
  115. {
  116. //var (reponse, m) = await _Agent.RechercheRAG(query, "Recheche RAG", model, retrievedChunks);
  117. var (reponse, m) = await _Agent.RechercheRAG(query, "Recheche RAG", model, ranked, domain.ToCollectionName());
  118. LoggerService.LogDebug("Fin de RAGService.RechercheDansDocuments avec demande au LLM");
  119. return (true, reponse, m);
  120. }
  121. else
  122. {
  123. LoggerService.LogDebug("Fin de RAGService.RechercheDansDocuments sans demande au LLM");
  124. return (true, "Pas de réponse contenue dans la base vectorielle", "Pas de LLM sollicité");
  125. }
  126. }
  127. /// <summary>
  128. /// Recherche dans les collections CV ou Emails
  129. /// </summary>
  130. /// <param name="domain"></param>
  131. /// <param name="query"></param>
  132. /// <returns></returns>
  133. public static async Task<string> Search(Domain domain, string query, string adresseMail="")
  134. {
  135. LoggerService.LogInfo("RAGService.Search");
  136. var qdrant = new QdrantService();
  137. //await ChargementDocuments(qdrant, false);
  138. // Cela inclut pour quey : le split en chunck + tokenisation + vectorisation
  139. (bool b, float[]? queryEmbedding) = await _Agent.GetEmbeddingAsync(query, true);
  140. if (b && queryEmbedding != null)
  141. {
  142. LoggerService.LogDebug("Embedding de la requête OK");
  143. }
  144. else
  145. {
  146. LoggerService.LogWarning("Problème lors de la vectorisation de la requête");
  147. return ("");
  148. }
  149. StringBuilder sb = new();
  150. var results = await qdrant.SearchAsync(domain, queryEmbedding,0.1f, topK: 20, adresseMail: adresseMail);
  151. /*
  152. var retrievedChunks = results
  153. .Select(r => r.Text)
  154. .ToArray();
  155. */
  156. var ranked = BgeReranker.Rerank(query, results, topK: 5);
  157. if (ranked.Count() > 0)
  158. {
  159. foreach (var entry in ranked)
  160. {
  161. sb.AppendLine($"Document: {entry.Nom_Fichier}");
  162. /*
  163. if (entry.Payload.TryGetValue("nom_fichier", out var value2))
  164. {
  165. if (value2.StringValue != "")
  166. {
  167. sb.AppendLine($"Email: {value2.StringValue}");
  168. }
  169. }
  170. */
  171. sb.AppendLine("------");
  172. }
  173. return (sb.ToString());
  174. }
  175. else
  176. {
  177. LoggerService.LogDebug("Fin de RAGService.RechercheDansDocuments sans demande au LLM");
  178. return ("");
  179. }
  180. }
  181. // Documents pas forcément RAG donc Domain en fonction du contexte
  182. public static async Task<bool> IngestDocument(Domain domain,bool isOnDemand, bool isRAZCollections, string content, string filename, string emailObject = "", string proprietaire = "", string niveauAcces="")
  183. {
  184. LoggerService.LogInfo("RAGService.IngestDocument");
  185. try
  186. {
  187. QdrantService qdrant = new();
  188. var chunks = ChunkText(content);
  189. await qdrant.InitializeCollectionsAsync(isRAZCollections);
  190. for (int i = 0; i < chunks.Length; i++)
  191. {
  192. var chunk = chunks[i];
  193. (bool b, float[]? embedding) = await _Agent.GetEmbeddingAsync(chunk, isOnDemand);
  194. if (b && embedding != null)
  195. {
  196. var id = $"{Path.GetFileName(filename)}_chunk_{i}";
  197. await qdrant.IngestDocument(domain, embedding, filename, id, chunk, emailObject, proprietaire, niveauAcces);
  198. }
  199. }
  200. return true;
  201. }
  202. catch (Exception ex)
  203. {
  204. LoggerService.LogError($"RAGService.IngestDocument : {ex.Message}");
  205. return false;
  206. }
  207. }
  208. public static string[] ChunkText(string text, int chunkSize = 250, int overlap = 50)
  209. {
  210. var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
  211. var chunks = new List<string>();
  212. /*
  213. for (int i = 0; i < words.Length; i += (chunkSize - overlap))
  214. {
  215. var chunkWords = words.Skip(i).Take(chunkSize);
  216. chunks.Add(string.Join(' ', chunkWords));
  217. }
  218. */
  219. int start = 0;
  220. while (start < words.Length)
  221. {
  222. int length = Math.Min(chunkSize, words.Length - start);
  223. var chunk = string.Join(' ', words.Skip(start).Take(length));
  224. chunks.Add(chunk);
  225. start += chunkSize - overlap;
  226. }
  227. return chunks.ToArray();
  228. }
  229. #endregion
  230. #region Méthodes privées
  231. // Pour l'instant Documents RAG
  232. private static async Task<bool> IngestDocuments(string pathDocs, string pathDestinationDocs, QdrantService qdrant, bool isRAZCollections)
  233. {
  234. LoggerService.LogInfo("RAGService.IngestDocuments");
  235. // boucler sur les documents et envoyer à _Agent.IngestDocumentAsync
  236. // Cela inclut le split en chunck + tokenisation + vectorisation + sauvegarde des vecteurs
  237. try
  238. {
  239. if (!System.IO.Directory.Exists(pathDocs))
  240. {
  241. LoggerService.LogWarning($"Le dossier n'existe pas : {pathDocs}");
  242. return false;
  243. }
  244. await qdrant.InitializeCollectionsAsync(isRAZCollections);
  245. List<string> filesToDeplace = new();
  246. var files = Directory.GetFiles(pathDocs, "*.*", SearchOption.AllDirectories);
  247. foreach (var file in files)
  248. {
  249. var relativePath = System.IO.Path.GetRelativePath(pathDocs, file);
  250. var content = FilesService.ExtractText(file);
  251. if (content != string.Empty)
  252. {
  253. var domain = DomainExtensions.GetDomainByPath(file);
  254. // Si ce document y est déjà, le contenu de la base vectorielle sera supprimé avant d'être ingéré de nouveau
  255. await qdrant.DeleteDocumentInQdrant(domain, Path.GetFileName(file));
  256. var chunks = ChunkText(content);
  257. for (int i = 0; i < chunks.Length; i++)
  258. {
  259. var chunk = chunks[i];
  260. (bool b, float[]? embedding) = await _Agent.GetEmbeddingAsync(chunk, true);
  261. if (b && embedding != null)
  262. {
  263. var id = $"{Path.GetFileName(file)}_chunk_{i}";
  264. await qdrant.IngestDocument(domain, embedding, Path.GetFileName(file), id, chunk);
  265. }
  266. }
  267. filesToDeplace.Add(file);
  268. }
  269. }
  270. foreach (var file in filesToDeplace)
  271. {
  272. // on déplace les fichiers une fois vectorisés
  273. // Chemin relatif pour recréer les sous-dossiers
  274. var relativePath = System.IO.Path.GetRelativePath(pathDocs, file);
  275. var destPath = System.IO.Path.Combine(pathDestinationDocs, relativePath);
  276. // Crée le dossier de destination si nécessaire
  277. var destDir = System.IO.Path.GetDirectoryName(destPath);
  278. if (destDir != null && !Directory.Exists(destDir))
  279. {
  280. Directory.CreateDirectory(destDir);
  281. }
  282. // Déplace le fichier
  283. if(System.IO.File.Exists(file))
  284. File.Move(file, destPath, overwrite: true);
  285. }
  286. LoggerService.LogDebug($"Fin de RAGService.IngestDocument : {files.Length} document(s)");
  287. return true;
  288. }
  289. catch (Exception ex)
  290. {
  291. LoggerService.LogError($"RAGService.IngestDocument : {ex.Message}");
  292. return false;
  293. }
  294. }
  295. #endregion
  296. }
  297. }