diff --git a/AiCoreUnitTest/AiCoreUnitTest.csproj b/AiCoreUnitTest/AiCoreUnitTest.csproj index 37b1ec8fa..f2c48c473 100644 --- a/AiCoreUnitTest/AiCoreUnitTest.csproj +++ b/AiCoreUnitTest/AiCoreUnitTest.csproj @@ -66,6 +66,10 @@ {69D70CA9-0DF9-419E-BFAF-F4539F129BD9} AICore + + {094331C3-ECEE-4C89-BBFD-4C9DED89F0EF} + Service + {2F50B83D-A3F0-4EC4-979A-3F9B7E3D8ED4} Shared diff --git a/AiCoreUnitTest/OllamaFacadeTest.cs b/AiCoreUnitTest/OllamaFacadeTest.cs index 57e2a1058..695a7b0ba 100644 --- a/AiCoreUnitTest/OllamaFacadeTest.cs +++ b/AiCoreUnitTest/OllamaFacadeTest.cs @@ -1,7 +1,10 @@ using AICore.Facade.LLM; +using BeWo.Service.Core; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.Logging; using System; using System.Net; +using System.Runtime.InteropServices; namespace AiCoreUnitTest { @@ -48,7 +51,8 @@ namespace AiCoreUnitTest } }; - var response = client.SendAiMessageRequest(payload, out string message, out string model, out int? duration); + var logger = ServiceLogger.GetRequestLogger(); + var response = client.SendAiMessageRequest(payload, out string message, out string model, out int? duration, logger); Assert.IsTrue(response?.Success); } diff --git a/BeWo/app.config b/BeWo/app.config index dc7575256..55003482e 100644 --- a/BeWo/app.config +++ b/BeWo/app.config @@ -13,19 +13,19 @@ - + - + - + diff --git a/Server/Components/AICore/Facade/LLM/BaseLLMApiClient.cs b/Server/Components/AICore/Facade/LLM/BaseLLMApiClient.cs index 4e016d020..846e51932 100644 --- a/Server/Components/AICore/Facade/LLM/BaseLLMApiClient.cs +++ b/Server/Components/AICore/Facade/LLM/BaseLLMApiClient.cs @@ -7,12 +7,29 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Text.Json; +using BS.Shared.Interface; +using BeWo.ServerUtils.ApiFacade; +using BS.Shared.Logger; namespace AICore.Facade.LLM { public abstract class BaseLLMApiClient { + private protected readonly HttpClientFacade _GetClientFacade; + private protected readonly JsonHttpClientFacade _PostClientFacade; + private protected readonly IApiErrorExtractor _ErrorExtractor; + + protected BaseLLMApiClient(string base_url, string api_key) + { + _GetClientFacade = new HttpClientFacade(base_url, api_key, false); + _PostClientFacade = new JsonHttpClientFacade(base_url, api_key, false) + { + Timeout = TimeSpan.FromMinutes(10) + }; + _ErrorExtractor = new DefaultErrorExtractor(); + } + public abstract ApiResponse> GetAiModelle(); - public abstract ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration); + public abstract ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null); } } diff --git a/Server/Components/AICore/Facade/LLM/OllamaApiClient.cs b/Server/Components/AICore/Facade/LLM/OllamaApiClient.cs index dc2cc9960..37f9c04d6 100644 --- a/Server/Components/AICore/Facade/LLM/OllamaApiClient.cs +++ b/Server/Components/AICore/Facade/LLM/OllamaApiClient.cs @@ -8,23 +8,16 @@ using System.Linq; using System.Net.Sockets; using System.Net; using BeWo.ServerUtils.ApiFacade; +using BS.Shared; +using BS.Shared.Logger; namespace AICore.Facade.LLM { public class OllamaApiClient : BaseLLMApiClient { - private protected readonly HttpClientFacade _GetClientFacade; - private protected readonly JsonHttpClientFacade _PostClientFacade; - private protected readonly IApiErrorExtractor _ErrorExtractor; - - public OllamaApiClient(string base_url, string api_key) + public OllamaApiClient(string base_url, string api_key) : base(base_url, api_key) { - _GetClientFacade = new HttpClientFacade(base_url, api_key, false); - _PostClientFacade = new JsonHttpClientFacade(base_url, api_key, false) - { - Timeout = TimeSpan.FromMinutes(10) - }; - _ErrorExtractor = new DefaultErrorExtractor(); + } public override ApiResponse> GetAiModelle() @@ -60,7 +53,7 @@ namespace AICore.Facade.LLM return ApiResponse>.SuccessResponse(data); } - public override ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration) + public override ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null) { message = null; duration = null; @@ -68,6 +61,12 @@ namespace AICore.Facade.LLM _PostClientFacade.JsonObject = payload; + if(logger is object) + { + logger.Log(LogLevel.Info, "Request:"); + logger.LogJson(LogLevel.Info, _PostClientFacade.JsonObject); + } + var response_format = new { id = string.Empty, @@ -89,6 +88,12 @@ namespace AICore.Facade.LLM var response = _PostClientFacade.GetAnonymousTypeAsync("api/ollama/chat/completions", response_format, _ErrorExtractor).GetAwaiter().GetResult(); + if(logger is object) + { + logger.Log(LogLevel.Info, "Response:"); + logger.LogJson(LogLevel.Info, response); + } + if (!response.Success) return response.Copy(); diff --git a/Server/Components/AICore/Facade/LLM/OpenWebApiClient.cs b/Server/Components/AICore/Facade/LLM/OpenWebApiClient.cs index 9ef7bc061..703a2f5bb 100644 --- a/Server/Components/AICore/Facade/LLM/OpenWebApiClient.cs +++ b/Server/Components/AICore/Facade/LLM/OpenWebApiClient.cs @@ -1,8 +1,10 @@ using BeWo.ServerUtils.ApiFacade; +using BS.Shared; using BS.Shared.DataContracts; using BS.Shared.DataContracts.Feature.AI; using BS.Shared.Extensions; using BS.Shared.Interface; +using BS.Shared.Logger; using System; using System.Collections.Generic; using System.IO; @@ -13,18 +15,9 @@ namespace AICore.Facade.LLM { public class OpenWebApiClient : BaseLLMApiClient { - private protected readonly HttpClientFacade _GetClientFacade; - private protected readonly JsonHttpClientFacade _PostClientFacade; - private protected readonly IApiErrorExtractor _ErrorExtractor; - - public OpenWebApiClient(string base_url, string api_key) + public OpenWebApiClient(string base_url, string api_key) : base(base_url, api_key) { - _GetClientFacade = new HttpClientFacade(base_url, api_key); - _PostClientFacade = new JsonHttpClientFacade(base_url, api_key) - { - Timeout = TimeSpan.FromMinutes(10) - }; - _ErrorExtractor = new DefaultErrorExtractor(); + } public override ApiResponse> GetAiModelle() @@ -60,7 +53,7 @@ namespace AICore.Facade.LLM return ApiResponse>.SuccessResponse(data); } - public override ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration) + public override ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null) { message = null; duration = null; @@ -68,6 +61,12 @@ namespace AICore.Facade.LLM _PostClientFacade.JsonObject = payload; + if (logger is object) + { + logger.Log(LogLevel.Info, "Request:"); + logger.LogJson(LogLevel.Info, _PostClientFacade.JsonObject); + } + var response_format = new { id = string.Empty, @@ -93,6 +92,12 @@ namespace AICore.Facade.LLM var response = _PostClientFacade.GetAnonymousTypeAsync("api/chat/completions", response_format, _ErrorExtractor).GetAwaiter().GetResult(); + if (logger is object) + { + logger.Log(LogLevel.Info, "Response:"); + logger.LogJson(LogLevel.Info, response); + } + if (!response.Success) return response.Copy(); diff --git a/Service/Core/PathHelper.cs b/Service/Core/PathHelper.cs index 4f27bac23..55069a236 100644 --- a/Service/Core/PathHelper.cs +++ b/Service/Core/PathHelper.cs @@ -9,14 +9,62 @@ namespace BeWo.Service.Core { public static class PathHelper { - #region AI - private static string GetAiPath() - => Path.Combine(getRoot(), "AI"); - public static string GetAiSystemPromptPath() - => Path.Combine(GetAiPath(), "systemPrompts"); - #endregion + /// + /// Holt den Ordner, wo sich die systemInstructions befinden + /// + public static string GetAiSystemInstructionPath() + => Path.Combine(getRoot(), "AI", "systemInstructions"); + + public static string GetOrCreateUniqueRequestLogFile() + { + var folder_path = Path.Combine(getExecutionRoot(), "logs", "ai", "requests"); + Directory.CreateDirectory(folder_path); + + var file_name = getUniqueFileName(folder_path, $"{DateTime.Now:yyyy.MM.dd HH_mm_ss}", 20); + + if (file_name == null) + return null; + + return Path.Combine(folder_path, file_name + ".log"); + } + + /// + /// Holt den Server Root Path + /// Normalerweise: C:/BeWoPlaner + /// private static string getRoot() => MergedConfig.GetSetting(WebConfigSetting.BeWoPlanerServiceRoot); + + /// + /// Holt den Service Root Path + /// Normalerweise: C:/BeWoPlaner/service/Host + /// + private static string getExecutionRoot() + => AppDomain.CurrentDomain.BaseDirectory; + + + private static string getUniqueFileName(string folderPath, string fileName, int retries) + { + var path = Path.Combine(folderPath, fileName); + + if (!File.Exists(path)) + return fileName; + + var name = 2; + while(retries-- > 0) + { + var new_fileName = fileName + $" ({name})"; + var new_path = Path.Combine(folderPath, new_fileName); + + if (!File.Exists(new_path)) + return new_fileName; + + name++; + } + + return null; + + } } } diff --git a/Service/Core/ServiceHelper.cs b/Service/Core/ServiceHelper.cs index 62288daa1..7bba45b55 100644 --- a/Service/Core/ServiceHelper.cs +++ b/Service/Core/ServiceHelper.cs @@ -8,16 +8,13 @@ using System.Threading.Tasks; using System.ServiceModel.Dispatcher; using BeWo.Service.ServiceUtils; using BS.Shared.Extensions; +using BS.Shared.Logger; using BeWo.ServerUtils; namespace BeWo.Service.Core { public static class ServiceHelper { - public static FileLogger CoreLogger { get; } = new FileLogger(MergedConfig.GetSetting(WebConfigSetting.ServiceLogFilePath)); - public static FileLogger WCFErrorLogger { get; } = new FileLogger(MergedConfig.GetSetting(WebConfigSetting.WCFErrorLogFilePath)); - public static FileLogger JsonErrorLogger { get; } = new FileLogger(MergedConfig.GetSetting(WebConfigSetting.JsonErrorLogFilePath)); - public static Tuple GetInterfaceAndMethodWCF(Message request, IClientChannel channel) { var lTemp = request.Headers.Action.Split("/"); diff --git a/Service/Core/ServiceLogger.cs b/Service/Core/ServiceLogger.cs new file mode 100644 index 000000000..f1f9a14f7 --- /dev/null +++ b/Service/Core/ServiceLogger.cs @@ -0,0 +1,27 @@ +using BS.Shared.Logger; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BeWo.Service.Core +{ + public static class ServiceLogger + { + public static FileLogger CoreLogger { get; } = new FileLogger(MergedConfig.GetSetting(WebConfigSetting.ServiceLogFilePath)); + public static FileLogger WCFErrorLogger { get; } = new FileLogger(MergedConfig.GetSetting(WebConfigSetting.WCFErrorLogFilePath)); + public static FileLogger JsonErrorLogger { get; } = new FileLogger(MergedConfig.GetSetting(WebConfigSetting.JsonErrorLogFilePath)); + + public static FileLogger GetRequestLogger() + { + var filePath = PathHelper.GetOrCreateUniqueRequestLogFile(); + + if (filePath == null) + throw new NotImplementedException("err98462"); + + return new FileLogger(filePath); + } + } +} diff --git a/Service/Plugins/AiFunctionService.cs b/Service/Plugins/AiFunctionService.cs index 9b9e52054..722900736 100644 --- a/Service/Plugins/AiFunctionService.cs +++ b/Service/Plugins/AiFunctionService.cs @@ -32,7 +32,7 @@ namespace BeWo.Service.Plugins // Anfrage zusammensetzen var context = AiFunctionContextLoader.LoadServiceRecordDocumentationContext(request); - var systemInstructionsFolderPath = PathHelper.GetAiSystemPromptPath(); + var systemInstructionsFolderPath = PathHelper.GetAiSystemInstructionPath(); var factory = new AiFunctionSystemInstructionBuilder(systemInstructionsFolderPath); var system_prompt = factory.CreateServiceRecordDocumentationSystemInstructions(context); @@ -76,7 +76,8 @@ namespace BeWo.Service.Plugins } }; - var response = ServiceFacade.OllamaApiClient.SendAiMessageRequest(payload, out string message, out string model, out int? duration); + var logger = ServiceLogger.GetRequestLogger(); + var response = ServiceFacade.OllamaApiClient.SendAiMessageRequest(payload, out string message, out string model, out int? duration, logger); return message; } diff --git a/Service/Plugins/AiService2.cs b/Service/Plugins/AiService2.cs index f84299bf9..416cbcff8 100644 --- a/Service/Plugins/AiService2.cs +++ b/Service/Plugins/AiService2.cs @@ -270,7 +270,7 @@ namespace BeWo.Service.Plugins { var current_user = UserRightHelper.GetLoggedInUserWithOid(); - var systemInstructionsFolderPath = PathHelper.GetAiSystemPromptPath(); + var systemInstructionsFolderPath = PathHelper.GetAiSystemInstructionPath(); var factory = new AiConversationSystemInstructionBuilder(systemInstructionsFolderPath); var context_loaded = AiContextLoader.LoadContext(uicontext, context_reference); @@ -343,13 +343,15 @@ namespace BeWo.Service.Plugins // Send Request, wait for Response var dt_before_request = DateTime.Now; + var logger = ServiceLogger.GetRequestLogger(); + ApiResponse response; string message2, model; int? duration; if (config.SelectedModel.ModelSource == AiModelSource.openwebui) - response = ServiceFacade.OpenWebApiClient.SendAiMessageRequest(payload, out message2, out model, out duration); + response = ServiceFacade.OpenWebApiClient.SendAiMessageRequest(payload, out message2, out model, out duration, logger); else if (config.SelectedModel.ModelSource == AiModelSource.ollama) - response = ServiceFacade.OllamaApiClient.SendAiMessageRequest(payload, out message2, out model, out duration); + response = ServiceFacade.OllamaApiClient.SendAiMessageRequest(payload, out message2, out model, out duration, logger); else throw new NotImplementedException(); diff --git a/Service/Plugins/XRechnungService.cs b/Service/Plugins/XRechnungService.cs index 1a7d9913b..e71637328 100644 --- a/Service/Plugins/XRechnungService.cs +++ b/Service/Plugins/XRechnungService.cs @@ -9,6 +9,7 @@ using BS.Shared; using BS.Shared.Core; using BS.Shared.DataContracts; using BS.Shared.Interface; +using BS.Shared.Logger; using DevExpress.Export; using DevExpress.Utils.Filtering.Internal; using Newtonsoft.Json; @@ -35,7 +36,7 @@ namespace BeWo.Service.Plugins _storage = DAOFactory.GetLocalTenantFileStorage(lStoragePath); _genericDAO = DAOFactory.GenericDAO; - _logger = ServiceHelper.CoreLogger; + _logger = ServiceLogger.CoreLogger; } public ApiResponse> GetXRechnung(long invoice_base_oid, int report_type, string report_url) diff --git a/Service/Service.csproj b/Service/Service.csproj index 20e6da829..0b6841bee 100644 --- a/Service/Service.csproj +++ b/Service/Service.csproj @@ -228,9 +228,9 @@ - + diff --git a/Service/ServiceBehavior/JsonError/JsonErrorHandler.cs b/Service/ServiceBehavior/JsonError/JsonErrorHandler.cs index 1e4242b14..ed9a09871 100644 --- a/Service/ServiceBehavior/JsonError/JsonErrorHandler.cs +++ b/Service/ServiceBehavior/JsonError/JsonErrorHandler.cs @@ -33,7 +33,7 @@ namespace BeWo.Service.ServiceBehavior.JsonError public void ProvideFault(Exception error, MessageVersion version, ref Message fault) { // Logge in File - ServiceHelper.JsonErrorLogger.LogError(error); + ServiceLogger.JsonErrorLogger.LogError(error); // Erstelle eine JSON-Fehlermeldung var jsonError = new JsonError diff --git a/Service/ServiceBehavior/WCFError/WCFErrorHandler.cs b/Service/ServiceBehavior/WCFError/WCFErrorHandler.cs index ced7ff2e9..d615ce977 100644 --- a/Service/ServiceBehavior/WCFError/WCFErrorHandler.cs +++ b/Service/ServiceBehavior/WCFError/WCFErrorHandler.cs @@ -25,7 +25,7 @@ namespace BeWo.Service.ServiceBehavior.WCFError public void ProvideFault(Exception error, MessageVersion version, ref Message message) { // Logge in File - ServiceHelper.WCFErrorLogger.LogError(error); + ServiceLogger.WCFErrorLogger.LogError(error); var wcfError = Utils.CreateBeWoFaultException(error); diff --git a/Service/Core/FileLogger.cs b/Shared/Logger/FileLogger.cs similarity index 96% rename from Service/Core/FileLogger.cs rename to Shared/Logger/FileLogger.cs index 7848727b6..ebd423170 100644 --- a/Service/Core/FileLogger.cs +++ b/Shared/Logger/FileLogger.cs @@ -3,7 +3,6 @@ using BS.Shared.Core; using BS.Shared.Exceptions; using BS.Shared.Interface; using Newtonsoft.Json; -using RestSharp.Validation; using System; using System.Collections.Generic; using System.IO; @@ -11,7 +10,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace BeWo.Service.Core +namespace BS.Shared.Logger { public class FileLogger { diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index a0c5b931c..026976987 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -429,6 +429,7 @@ +