|
- using Services.Models;
- using System.Diagnostics;
- using System.Net.Http.Json;
- using System.Text;
- using System.Text.Json;
- using System.Text.RegularExpressions;
- using ToolsServices;
- using OllamaService.Models;
-
- namespace Services.Ollama
- {
- public static class OllamaService
- {
- #region Variables
- private static HttpClient? _HttpClient;
- private static readonly string NomFichierData = FichiersInternesService.ParamsOllama;// "paramsOllama.txt";
- private static List<string> ImagesFullFilename = new();
- #endregion
-
- #region Méthodes publiques
-
- #region InterActions
- /// <summary>
- /// Vérifie si Ollama est actif
- /// </summary>
- /// <param name="isOnDemand"></param>
- /// <returns></returns>
- public static async Task<bool> IsOllamaActif(bool isOnDemand)
- {
- var precisionContext = isOnDemand ? "OnDemand" : "Batch";
- LoggerService.LogInfo($"OllamaService.IsOllamaActif {precisionContext}");
-
- try
- {
- ParametresOllamaService? ollama = LoadParametres();
- if (ollama == null)
- {
- var msg = "Erreur de chargement des paramètres Ollama.";
- LoggerService.LogWarning(msg);
- return false;
- }
- var url = "";
- if(isOnDemand)
- url = $"{ollama.Ollama_URL}/api/version";
- else
- url = $"{ollama.Ollama_Batch_URL}/api/version";
- _HttpClient = new HttpClient();
-
- _HttpClient.Timeout = TimeSpan.FromSeconds(30);
- var response = await _HttpClient.GetAsync(url);
- LoggerService.LogDebug($"OllamaService.IsOllamaActif {precisionContext} : {response.IsSuccessStatusCode}");
- return response.IsSuccessStatusCode;
- }
- catch(Exception ex)
- {
- LoggerService.LogError($"OllamaService.IsOllamaActif {precisionContext} : False --> {ex.Message}");
- return false;
- }
- }
-
- /// <summary>
- /// Appelle l'api Generate de Ollama
- /// </summary>
- /// <param name="prompt"></param>
- /// <param name="precision"></param>
- /// <param name="model"></param>
- /// <param name="isOnDemand"></param>
- /// <returns></returns>
- public static async Task<(bool, string)> GenererAsync(string prompt, string precision, string model, bool isOnDemand)
- {
- var precisionContext = isOnDemand ? "OnDemand" : "Batch";
- LoggerService.LogInfo($"OllamaService.GenererAsync {precisionContext} : {model}");
- string? sReturn = "";
- Stopwatch chrono = new Stopwatch();
- chrono.Start();
- bool isSuccess = true;
- ParametresOllamaService? ollama = LoadParametres();
- if (ollama == null)
- {
- var msg = "Erreur de chargement des paramètres Ollama.";
- LoggerService.LogWarning(msg);
- return (false, msg);
- }
-
- try
- {
- //prompt = "Donne tes réponses dans la langue Française." + "\n" + prompt;
- prompt = PromptService.GetPrompt(PromptService.ePrompt.OllamaService_PromptSystem) + "\n" + prompt;
- var requestBody = new
- {
- model = model,
- prompt = TruncatePromptBySentenceAsync(prompt),
- temperature = isOnDemand ? ollama.Ollama_Temperature : ollama.Ollama_Batch_Temperature, // → 0 : réponses plus précises et conservatrices ; 1 : plus créatives
- top_p = isOnDemand ? ollama.Ollama_Top_p : ollama.Ollama_Batch_Top_p, // → on considère uniquement les mots parmi les plus probables (limite l’invention)
- max_tokens = isOnDemand ? ollama.Ollama_Max_tokens : ollama.Ollama_Batch_Max_tokens, // → pour ne pas générer des réponses trop longues
- stream = false
- };
- var url = isOnDemand ? ollama.Ollama_URL : ollama.Ollama_Batch_URL;
- int timeOut = isOnDemand ? ollama.Ollama_TimeOut : ollama.Ollama_Batch_TimeOut;
-
- url = $"{url}/api/generate";
-
- LoggerService.LogDebug($"Génération Ollama : {precision} - Model : {model} - URL : {url} - Time-out : {timeOut}");
- LoggerService.LogDebug($"Prompt : {requestBody.prompt}");
-
- _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(timeOut) };
-
- var response = await _HttpClient.PostAsJsonAsync($"{url}", requestBody);
-
- if (!response.IsSuccessStatusCode)
- {
- var err = await response.Content.ReadAsStringAsync();
- var msg = $"Erreur Ollama({response.StatusCode}) : {err}";
- LoggerService.LogWarning(msg);
- return (false, $"{msg}");
- }
-
- var json = await response.Content.ReadFromJsonAsync<JsonElement>();
- sReturn = json!.GetProperty("response")!.GetString();
- if (sReturn != null)
- {
- sReturn = EpureReponse(sReturn);
- }
-
- LoggerService.LogDebug($"OllamaService.GenerateAsync : {sReturn}");
-
- }
- catch (HttpRequestException ex)
- {
- isSuccess = false;
- LoggerService.LogError("Erreur HTTP : " + ex.Message);
- }
- catch (OperationCanceledException)
- {
- isSuccess = false;
- LoggerService.LogError($"La requête a été annulée après {ollama.Ollama_TimeOut} secondes.");
- }
- catch (Exception ex)
- {
- isSuccess = false;
- sReturn = $"Erreur lors de la génération : {ex.Message}";
- LoggerService.LogError(sReturn);
- //return $"{msg}";
- }
- finally
- {
- chrono.Stop();
- LoggerService.LogInfo($"Temps de la génération : {chrono.ElapsedMilliseconds / 1000} s ({precision})");
-
- }
- return (isSuccess,$"{sReturn}");
- }
-
- /// <summary>
- /// Appelle l'api Chat de Ollama
- /// </summary>
- /// <param name="model"></param>
- /// <param name="messages"></param>
- /// <returns></returns>
- public static async Task<(bool, string)> ChatAsync(string model, List<ChatMessage> messages)
- {
- LoggerService.LogInfo($"OllamaService.SendMessageAsync : {model}");
- Stopwatch chrono = new Stopwatch();
- chrono.Start();
- string? sReturn = "";
- bool isSuccess = true;
- ParametresOllamaService? ollama = LoadParametres();
- if (ollama == null)
- {
- var msg = "Erreur de chargement des paramètres Ollama.";
- LoggerService.LogWarning(msg);
- return (false, msg);
- }
-
- try
- {
- var promptSystem = PromptService.GetPrompt(PromptService.ePrompt.OllamaService_PromptSystem);
- var systemPrompt = new ChatMessage { Role = "system", Content = promptSystem, Model = model };
- var allMessages = new List<ChatMessage> { systemPrompt };
-
- messages[0].Content = promptSystem + "\n" + messages[0].Content;
- allMessages.AddRange(messages);
-
- var request = new
- {
- model = model,
- messages = allMessages.Select(m => new { role = m.Role, content = m.Content }).ToList(),
- temperature = ollama.Ollama_Temperature, // → 0 : réponses plus précises et conservatrices ; 1 : plus créatives
- top_p = ollama.Ollama_Top_p, // → on considère uniquement les mots parmi les plus probables (limite l’invention)
- max_tokens = ollama.Ollama_Max_tokens, // → pour ne pas générer des réponses trop longues
- stream = false
- };
-
- var url = $"{ollama.Ollama_URL}/api/chat";
- LoggerService.LogDebug($"Génération Ollama : CHAT - Model : {model} - URL : {url} - Time-out : {ollama.Ollama_TimeOut}");
- LoggerService.LogDebug($"Prompt : {request.messages[1].content}");
-
- _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(ollama.Ollama_TimeOut) };
-
- var response = await _HttpClient.PostAsync(url, new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"));
-
- if (!response.IsSuccessStatusCode)
- {
- var err = await response.Content.ReadAsStringAsync();
- var msg = $"Erreur Ollama({response.StatusCode}) : {err}";
- LoggerService.LogWarning(msg);
- return (false, $"{msg}");
- }
-
- var result = await response.Content.ReadAsStringAsync();
- using var doc = JsonDocument.Parse(result);
- sReturn = doc.RootElement.GetProperty("message").GetProperty("content").GetString();
- if (sReturn != null)
- {
- sReturn = EpureReponse(sReturn);
- }
- LoggerService.LogDebug($"OllamaService.SendMessageAsync : {sReturn}");
-
- }
-
- catch (Exception ex)
- {
- isSuccess = false;
- sReturn = $"Erreur lors de la génération : {ex.Message}";
- LoggerService.LogError(sReturn);
- }
-
- finally
- {
- chrono.Stop();
- LoggerService.LogInfo($"Temps de la génération : {chrono.ElapsedMilliseconds / 1000} s (LLM)");
-
- if (sReturn == null)
- sReturn = "";
- }
- return (isSuccess, $"{sReturn}");
- }
-
- /// <summary>
- /// Appelle l'api Generate de Ollama avec envoi d'images dans le prompt
- /// </summary>
- /// <param name="model"></param>
- /// <param name="prompt"></param>
- /// <param name="imagesFullFilename"></param>
- /// <returns></returns>
- public static async Task<(bool, string)> GenererAsync(string model, string prompt, List<string> imagesFullFilename)
- {
- LoggerService.LogInfo($"OllamaService.SendMessageAsync : {model}");
- Stopwatch chrono = new Stopwatch();
- chrono.Start();
- bool isSuccess = true;
- var imagesBase64 = new List<string>();
-
- ParametresOllamaService? ollama = LoadParametres();
- if (ollama == null)
- {
- var msg = "Erreur de chargement des paramètres Ollama.";
- LoggerService.LogWarning(msg);
- return (false, msg);
- }
-
- foreach (var uneImage in imagesFullFilename)
- {
- byte[] imageBytes = File.ReadAllBytes(uneImage);
- imagesBase64.Add(Convert.ToBase64String(imageBytes));
- }
- var request = new OllamaRequest
- {
- Model = model,
- Prompt = PromptService.GetPrompt(PromptService.ePrompt.OllamaService_PromptSystem) + "\n" + prompt,
- Temperature = ollama.Ollama_Temperature, // → 0 : réponses plus précises et conservatrices ; 1 : plus créatives
- Top_p = ollama.Ollama_Top_p, // → on considère uniquement les mots parmi les plus probables (limite l’invention)
- Max_tokens = ollama.Ollama_Max_tokens, // → pour ne pas générer des réponses trop longues
- Images = imagesBase64,
- Stream = false
- };
-
- string? sReturn = "";
-
-
- try
- {
-
- var url = $"{ollama.Ollama_URL}/api/generate";
- LoggerService.LogDebug($"Prompt envoyé à {url}");
- _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(ollama.Ollama_TimeOut) };
-
- var response = await _HttpClient.PostAsJsonAsync(url, request);
-
- if (!response.IsSuccessStatusCode)
- {
- var err = await response.Content.ReadAsStringAsync();
- var msg = $"Erreur Ollama({response.StatusCode}) : {err}";
- LoggerService.LogWarning(msg);
- return (false, $"{msg}");
- }
-
- var result = await response.Content.ReadFromJsonAsync<OllamaResponse>();
- if (result != null)
- {
- sReturn = result.Completion.Trim();
- sReturn = EpureReponse(sReturn);
- }
- LoggerService.LogDebug($"OllamaService.SendMessageAsync : {sReturn}");
-
- }
- catch (Exception ex)
- {
- isSuccess=false;
- sReturn = $"Erreur lors de la génération : {ex.Message}";
- LoggerService.LogError(sReturn);
- //return sReturn;
- }
- finally
- {
- chrono.Stop();
- LoggerService.LogInfo($"Temps de la génération : {chrono.ElapsedMilliseconds / 1000} s (Interprétation images)");
-
- if (sReturn == null)
- sReturn = "";
- }
- return (isSuccess, $"{sReturn}");
- }
-
- #endregion
-
- #region Gestion des paramètres
- public static ParametresOllamaService? LoadParametres()
- {
- LoggerService.LogInfo("OllamaService.LoadParametres");
- ParametresOllamaService SelectedItem = new();
-
- try
- {
- if (File.Exists(NomFichierData))
- {
-
- string[] lignes = File.ReadAllLines(NomFichierData);
-
- if (lignes.Length > 0)
- {
- SelectedItem.Ollama_URL = lignes[0];
- }
- if (lignes.Length > 1)
- {
- if(int.TryParse(lignes[1], out int timeout))
- SelectedItem.Ollama_TimeOut = timeout;
- }
- if (lignes.Length > 2)
- {
- if (double.TryParse(lignes[2], out double temperature))
- SelectedItem.Ollama_Temperature = temperature;
- }
- if (lignes.Length > 3)
- {
- if (double.TryParse(lignes[3], out double top_p))
- SelectedItem.Ollama_Top_p = top_p;
- }
- if (lignes.Length > 4)
- {
- if (int.TryParse(lignes[4], out int max_tokens))
- SelectedItem.Ollama_Max_tokens = max_tokens;
- }
-
-
- if (lignes.Length > 5)
- {
- SelectedItem.Ollama_Batch_URL = lignes[5];
- }
- if (lignes.Length > 6)
- {
- if (int.TryParse(lignes[6], out int timeout))
- SelectedItem.Ollama_Batch_TimeOut = timeout;
- }
- if (lignes.Length > 7)
- {
- if (double.TryParse(lignes[7], out double temperature))
- SelectedItem.Ollama_Batch_Temperature = temperature;
- }
- if (lignes.Length > 8)
- {
- if (double.TryParse(lignes[8], out double top_p))
- SelectedItem.Ollama_Batch_Top_p = top_p;
- }
- if (lignes.Length > 9)
- {
- if (int.TryParse(lignes[9], out int max_tokens))
- SelectedItem.Ollama_Batch_Max_tokens = max_tokens;
- }
-
- }
- return SelectedItem;
-
- }
- catch
- {
- return null;
- }
- }
-
- public static bool SaveParametres(ParametresOllamaService selectedItem)
- {
- LoggerService.LogInfo("OllamaService.SaveParametres");
- try
- {
- StringBuilder sb = new();
- sb.AppendLine(selectedItem.Ollama_URL);
- sb.AppendLine(selectedItem.Ollama_TimeOut.ToString());
- sb.AppendLine(selectedItem.Ollama_Temperature.ToString());
- sb.AppendLine(selectedItem.Ollama_Top_p.ToString());
- sb.AppendLine(selectedItem.Ollama_Max_tokens.ToString());
-
- sb.AppendLine(selectedItem.Ollama_Batch_URL);
- sb.AppendLine(selectedItem.Ollama_Batch_TimeOut.ToString());
- sb.AppendLine(selectedItem.Ollama_Batch_Temperature.ToString());
- sb.AppendLine(selectedItem.Ollama_Batch_Top_p.ToString());
- sb.AppendLine(selectedItem.Ollama_Batch_Max_tokens.ToString());
-
- File.WriteAllText(NomFichierData, sb.ToString());
-
- return true;
- }
- catch
- {
- return false;
- }
-
- }
-
- public static async Task<List<OllamaModel>> GetInstalledModelsAsync(bool isOnDemand)
- {
- LoggerService.LogInfo("OllamaService.GetInstalledModelsAsync");
- try
- {
- ParametresOllamaService? ollama = LoadParametres();
- if (ollama == null)
- {
- return new List<OllamaModel> { new OllamaModel { Name = "Erreur de chargement des paramètres Ollama." } };
- }
-
- var url = "";
- int timeOut = 360;
- if (isOnDemand)
- {
- url = $"{ollama.Ollama_URL}/api/tags";
- timeOut = ollama.Ollama_TimeOut;
- }
- else
- {
- url = $"{ollama.Ollama_Batch_URL}/api/tags";
- timeOut = ollama.Ollama_Batch_TimeOut;
- }
- _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(timeOut) };
-
- var response = await _HttpClient.GetAsync($"{url}");
- LoggerService.LogDebug($"Requête envoyée à {url}");
- response.EnsureSuccessStatusCode();
-
- var json = await response.Content.ReadAsStringAsync();
- var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = false };
- var result = JsonSerializer.Deserialize<OllamaTagsResponse>(json, options);
-
- if (result?.Models == null)
- {
- return new List<OllamaModel> { new OllamaModel { Name = "Erreur de chargement des paramètres Ollama." } };
- }
-
- return result.Models;
- }
- catch (Exception ex)
- {
- LoggerService.LogError($"Erreur lors de la récupération des modèles installés : {ex.Message}");
- return new List<OllamaModel> { new OllamaModel { Name = $"Erreur : {ex.Message}" } };
- }
- }
-
- public static string GetModeleIA(string useCase)
- {
- LoggerService.LogInfo("OllamaService.GetModeleIA");
-
- ModelSelector selector = new();
- var models = selector.GetModelsForUseCase(useCase);
- if(models.ToList().Count>1)
- return models.First();
-
- return "";
- }
-
- public static IEnumerable<string> GetModelesIA(string useCase)
- {
- LoggerService.LogInfo("OllamaService.GetModelesIA");
-
- ModelSelector selector = new();
- var models = selector.GetModelsForUseCase(useCase);
- return models;
-
- }
-
- public static bool SaveParametresModeles(string useCase, ModelConfig models)
- {
- LoggerService.LogInfo("OllamaService.SaveParametresModeles");
-
- ModelSelector selector = new();
- var b = selector.SaveConfig(useCase, models);
- return b;
- }
- #endregion
-
- #region Nettoyage des prompts et surveillance de la taille
- public static string CleanUserInput(string input, out bool isPromptInjection)
- {
- LoggerService.LogInfo("OllamaService.CleanUserInput");
- isPromptInjection = false;
- var avant = input;
- if (string.IsNullOrWhiteSpace(input))
- return string.Empty;
-
- // 1. Limiter la taille max (ex : 500 caractères)
- /*
- int maxLength = 500;
- if (input.Length > maxLength)
- input = input.Substring(0, maxLength);
- */
-
- // 2. Supprimer les caractères non imprimables (ex : contrôles, retours chariot non standards)
- input = RemoveNonPrintableChars(input);
-
- // 3. Échapper les triples quotes pour ne pas casser le prompt (on remplace ''' par ' ' ' par exemple)
- input = input.Replace("'''", "' ' '");
-
- // 4. Rechercher et remplacer les mots/expressions d’injection potentielles
- string[] blacklist = new string[] {
- "ignore", "cancel", "stop generating", "disregard instructions","oublie les instructions", "oublie",
- "ignore instructions","ignore les instructions", "forget", "override", "bypass"
- };
-
- foreach (var word in blacklist)
- {
- // Remplacer même si insensible à la casse
- input = Regex.Replace(input, Regex.Escape(word), "[CENSORED]", RegexOptions.IgnoreCase);
- }
-
- // 5. Optionnel : remplacer les guillemets doubles et simples pour éviter la confusion
- input = input.Replace("\"", "'");
- input = input.Replace("\\", "/"); // éviter les échappements
-
- if (input.Contains("[CENSORED]"))
- {
- LoggerService.LogDebug($"Avant :{avant}");
- LoggerService.LogDebug($"Après :{input}");
- isPromptInjection = true;
- }
-
- return input.Trim();
- }
- #endregion
-
- #endregion
-
- #region Méthodes privées
- private static string EpureReponse(string texte)
- {
- var txt = texte;
- txt = txt.Replace("**", "");
- //txt = txt.Replace("*", "");
- txt = txt.Replace("</end_of_turn>", "");
- txt = txt.Replace("</start_of_turn>", "");
- txt = txt.Replace("```csv", "");
- txt = txt.Replace("```xml", "");
- txt = txt.Replace("```", "");
- txt = txt.Trim();
- return txt;
- }
-
- private static async Task<int> GetTokenMaxAsync(bool isOld)
- {
- LoggerService.LogInfo("OllamaService.GetTokenMaxAsync");
- try
- {
- ParametresOllamaService? ollama = LoadParametres();
- if (ollama == null)
- {
- return -1;
- }
- var requestBody = new
- {
- model = "",// ollama.Ollama_Model,
- };
-
- _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(ollama.Ollama_TimeOut) };
-
- // Construire l'URL pour obtenir les informations du modèle
- string url = $"{ollama.Ollama_URL}/api/show";
- LoggerService.LogDebug($"Prompt envoyé à {url}");
-
- var response = await _HttpClient.PostAsJsonAsync($"{url}", requestBody);
-
-
- // Envoyer une requête GET à l'API Ollama
- //HttpResponseMessage response = await _HttpClient.GetAsync(url);
-
- // Vérifier le statut de la réponse
- if (response.IsSuccessStatusCode)
- {
- // Lire le contenu de la réponse
- string responseBody = await response.Content.ReadAsStringAsync();
-
- var json = await response.Content.ReadFromJsonAsync<JsonElement>();
- var contextLength = json
- .GetProperty("model_info")
- .GetProperty("llama.context_length")
- .GetInt32();
-
- return contextLength;
-
- //return maxContextSize.HasValue ? (int)maxContextSize : 0;
- }
- else
- {
- var model = "";//ollama.Ollama_Model
- LoggerService.LogError($"Erreur lors de la récupération des informations du modèle {model}: {response.StatusCode}");
- return -1;
- }
- }
- catch (Exception ex)
- {
- LoggerService.LogError($"Erreur lors de la récupération du modèle : {ex.Message}");
- return -3; // Erreur lors de la récupération du modèle
- }
- }
-
- // Supprime les caractères non imprimables
- private static string RemoveNonPrintableChars(string text)
- {
- LoggerService.LogInfo("OllamaService.RemoveNonPrintableChars");
- var sb = new StringBuilder();
- foreach (char c in text)
- {
- // Exclure uniquement les caractères de contrôle ASCII (< 32), sauf les classiques utiles
- if (!char.IsControl(c) || c == '\n' || c == '\r' || c == '\t')
- {
- sb.Append(c);
- }
- }
- return sb.ToString();
- }
-
- private static int GetTokenMaxAsync()
- {
- LoggerService.LogInfo("OllamaService.GetTokenMaxAsync");
- try
- {
- //await Task.Delay(1);
- return 4096;
- }
- catch (Exception ex)
- {
- LoggerService.LogError($"Erreur lors de la récupération du modèle : {ex.Message}");
- return -3; // Erreur lors de la récupération du modèle
- }
- }
-
- private static int CountTokensAsync(string prompt)
- {
- LoggerService.LogInfo("OllamaService.CountTokensAsync");
- try
- {
- // 1 token, en langue francaise, c'est 1.25 mots (environ)
- int estimatedTokens = prompt.Split(' ').Length * 5 / 4;
- return estimatedTokens;
- }
- catch (Exception ex)
- {
- LoggerService.LogError($"Erreur lors de la récupération du modèle : {ex.Message}");
- return -1; // Erreur lors de la récupération du modèle
- }
- }
-
- private static string TruncatePromptBySentenceAsync(string prompt)
- {
- LoggerService.LogInfo("OllamaService.TruncatePromptBySentenceAsync");
-
- prompt = prompt.Trim();
- LoggerService.LogDebug($"Taille avant : {prompt.Length}");
-
- int reserveReponse = 512 * 3; // Réserve pour la réponse
- var maxTokens = GetTokenMaxAsync();
- var nbrTokens = CountTokensAsync(prompt);
- // Cas simple : le prompt tient dans la limite
- // On garde 512 * 3 de réserve pour la réponse
- if ((nbrTokens + reserveReponse) <= maxTokens)
- {
- LoggerService.LogDebug($"Taille après : {prompt.Length}");
- return prompt;
- }
-
- // Découpe le texte en phrases (naïvement en utilisant les points)
- var sentences = prompt.Split(new[] { ". ", "? ", "! " }, StringSplitOptions.RemoveEmptyEntries);
-
- StringBuilder builder = new();
- foreach (var sentence in sentences)
- {
- var next = builder.Length > 0 ? builder + ". " + sentence.Trim() : sentence.Trim();
-
- var tokenCount = CountTokensAsync(next.ToString());
- if ((tokenCount + reserveReponse) > maxTokens)
- {
- break; // Trop long, on s'arrête
- }
-
- builder.Clear();
- builder.Append(next);
- }
-
- var rep = builder.ToString().Trim();
- LoggerService.LogDebug($"Taille après : {rep.Length}");
- return rep;// + "\n\n[Tronqué pour respecter la limite de contexte]";
- }
- #endregion
- }
- }
|