This commit is contained in:
2025-12-02 15:40:12 +01:00
parent 6649fb1350
commit edc9dfd565
17 changed files with 161 additions and 50 deletions

View File

@@ -66,6 +66,10 @@
<Project>{69D70CA9-0DF9-419E-BFAF-F4539F129BD9}</Project>
<Name>AICore</Name>
</ProjectReference>
<ProjectReference Include="..\Service\Service.csproj">
<Project>{094331C3-ECEE-4C89-BBFD-4C9DED89F0EF}</Project>
<Name>Service</Name>
</ProjectReference>
<ProjectReference Include="..\Shared\Shared.csproj">
<Project>{2F50B83D-A3F0-4EC4-979A-3F9B7E3D8ED4}</Project>
<Name>Shared</Name>

View File

@@ -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);
}

View File

@@ -13,19 +13,19 @@
<basicHttpBinding>
<binding name="BeWoBasicEndpoint" closeTimeout="00:05:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true" messageEncoding="Text">
<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="Transport" />
<security mode="None" />
<!--security mode="Transport"/-->
<!-- mode="Transport" für https und "None" sonst-->
</binding>
<binding name="BeWoStreamingEndpoint" receiveTimeout="10:10:00" sendTimeout="10:01:00" maxBufferSize="65536" maxReceivedMessageSize="67108864" transferMode="StreamedRequest" useDefaultWebProxy="true" messageEncoding="Text">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="Transport" />
<security mode="None" />
<!--security mode="Transport"/-->
<!-- mode="Transport" für https und "None" sonst-->
</binding>
<binding name="QueryServiceEndpoint" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true" messageEncoding="Text">
<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="Transport">
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
<message clientCredentialType="UserName" algorithmSuite="Default"/>
</security>

View File

@@ -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<IEnumerable<AiModelDC>> GetAiModelle();
public abstract ApiResponse<string> SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration);
public abstract ApiResponse<string> SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null);
}
}

View File

@@ -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<IEnumerable<AiModelDC>> GetAiModelle()
@@ -60,7 +53,7 @@ namespace AICore.Facade.LLM
return ApiResponse<IEnumerable<AiModelDC>>.SuccessResponse(data);
}
public override ApiResponse<string> SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration)
public override ApiResponse<string> 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<string>();

View File

@@ -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<IEnumerable<AiModelDC>> GetAiModelle()
@@ -60,7 +53,7 @@ namespace AICore.Facade.LLM
return ApiResponse<IEnumerable<AiModelDC>>.SuccessResponse(data);
}
public override ApiResponse<string> SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration)
public override ApiResponse<string> 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<string>();

View File

@@ -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
/// <summary>
/// Holt den Ordner, wo sich die systemInstructions befinden
/// </summary>
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");
}
/// <summary>
/// Holt den Server Root Path
/// Normalerweise: C:/BeWoPlaner
/// </summary>
private static string getRoot()
=> MergedConfig.GetSetting(WebConfigSetting.BeWoPlanerServiceRoot);
/// <summary>
/// Holt den Service Root Path
/// Normalerweise: C:/BeWoPlaner/service/Host
/// </summary>
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;
}
}
}

View File

@@ -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<string, string> GetInterfaceAndMethodWCF(Message request, IClientChannel channel)
{
var lTemp = request.Headers.Action.Split("/");

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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<string> 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();

View File

@@ -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<Tuple<StoredFileDC, byte[]>> GetXRechnung(long invoice_base_oid, int report_type, string report_url)

View File

@@ -228,9 +228,9 @@
<Compile Include="Core\DiamantExporter.cs" />
<Compile Include="Core\ExcelDownload.cs" />
<Compile Include="Core\FileAttachmentUtils.cs" />
<Compile Include="Core\FileLogger.cs" />
<Compile Include="Core\MergedConfig.cs" />
<Compile Include="Core\PathHelper.cs" />
<Compile Include="Core\ServiceLogger.cs" />
<Compile Include="Core\ServiceLogic.cs" />
<Compile Include="Core\SettingsLogic.cs" />
<Compile Include="Core\Utils.cs" />

View File

@@ -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

View File

@@ -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);

View File

@@ -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
{

View File

@@ -429,6 +429,7 @@
<Compile Include="Interface\IInvoiceItem.cs" />
<Compile Include="Interface\ISupportConcept.cs" />
<Compile Include="Interface\IStoredFile.cs" />
<Compile Include="Logger\FileLogger.cs" />
<Compile Include="Models\AddressInformation.cs" />
<Compile Include="Packet.cs" />
<Compile Include="ReportRequests\AbstractReportRequest.cs" />