Zusätzliche Checkups eingebunden

This commit is contained in:
2026-07-24 12:21:11 +02:00
parent a4e42b8388
commit eeacc08472
13 changed files with 191 additions and 79 deletions

View File

@@ -1,5 +1,7 @@
using AICore.Facade.LLM;
using BeWo.Data.Security;
using BeWo.Service.Core;
using BS.Shared.Tests.Objects;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AiCoreUnitTest
@@ -32,19 +34,9 @@ namespace AiCoreUnitTest
var logger = ServiceLogger.GetRequestLogger(out string id);
// Build Payload
var payload = new
{
model = "gemma3:12b",
messages = new[] {
new {
role = "user",
message = "why is the sky blue?"
}
},
requestid = id
};
var payload = AiTestObjects.GetExampleSendRequest(UserRightHelper.GetTenant(), id);
var response = Client.SendAiMessageRequest(payload, out string message, out string model, out int? duration, logger);
var response = Client.SendAiMessageRequest(payload, out LLMResponse llm_response, logger);
Assert.IsTrue(response?.Success);
}

View File

@@ -1,6 +1,7 @@
using AICore.Facade.LLM;
using BeWo.Data.Security;
using BeWo.Service.Core;
using BS.Shared.Tests.Objects;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.Logging;
using System;
@@ -42,20 +43,9 @@ namespace AiCoreUnitTest
var logger = ServiceLogger.GetRequestLogger(out string id);
// Build Payload
var payload = new
{
model = "llama3:latest",
messages = new[] {
new {
role = "user",
message = "Say 'Hi'!"
}
},
customerid = UserRightHelper.GetTenant(),
requestid = id
};
var payload = AiTestObjects.GetExampleSendRequest(UserRightHelper.GetTenant(), id);
var response = client.SendAiMessageRequest(payload, out string message, out string model, out int? duration, logger);
var response = client.SendAiMessageRequest(payload, out LLMResponse llm_response, logger);
Assert.IsTrue(response?.Success);
}

View File

@@ -112,6 +112,7 @@
<Compile Include="Facade\AiPromptsApiClient.cs" />
<Compile Include="Facade\DefaultErrorExtractor.cs" />
<Compile Include="Facade\LLM\BaseLLMApiClient.cs" />
<Compile Include="Facade\LLM\LLMResponse.cs" />
<Compile Include="Facade\LLM\OllamaApiClient.cs" />
<Compile Include="Facade\LLM\OpenWebApiClient.cs" />
<Compile Include="Prompt\AiSystemPromptFactory.cs" />

View File

@@ -32,20 +32,20 @@ namespace AICore.Core
switch (mode)
{
case ContextSizeAnalyzeMode.InitContextSize:
valid = CalculateContextSize(json, (int)(max_context_size * MAX_TOKENS_SYSTEM_PERCENTAGE), out token_count);
valid = CalculateContextSize(json, max_context_size, MAX_TOKENS_SYSTEM_PERCENTAGE, out token_count);
break;
case ContextSizeAnalyzeMode.FirstMessage:
valid = CalculateContextSize(json, (int)(max_context_size * MAX_TOKENS_FIRST_MESSAGE_PERCENTAGE), out token_count);
valid = CalculateContextSize(json, max_context_size, MAX_TOKENS_FIRST_MESSAGE_PERCENTAGE, out token_count);
break;
case ContextSizeAnalyzeMode.ConversationMessage:
valid = CalculateContextSize(json, (int)(max_context_size * MAX_TOKENS_CONVERSATION_PERCENTAGE), out token_count);
valid = CalculateContextSize(json, max_context_size, MAX_TOKENS_CONVERSATION_PERCENTAGE, out token_count);
break;
}
return valid;
}
private static bool CalculateContextSize(string json, int context_size, out int token_size)
private static bool CalculateContextSize(string json, int context_size, double proportion, out int token_size)
{
token_size = 0;
@@ -54,7 +54,12 @@ namespace AICore.Core
token_size = (int)(json.Length / 3.5);
if (token_size < context_size)
if (context_size <= 0)
return true;
var relative_context_size = (int)(context_size * proportion);
if (token_size < relative_context_size)
return true;
// try1 (grobe Zeichen-Heuristik) liegt bereits über dem Limit -
@@ -62,7 +67,7 @@ namespace AICore.Core
var cl100k = _cl100kEncoding.Value.Encode(json).Count;
token_size = cl100k - (cl100k / 10);
if (token_size < context_size)
if (token_size < relative_context_size)
return true;
return false;
@@ -73,8 +78,11 @@ namespace AICore.Core
return estimated_tokens >= 2 * prompt_eval_count;
}
public static bool CheckForAssistent(int eval_count, int prompt_eval_count, int context_size)
public static bool CheckForValidAssistent(int eval_count, int prompt_eval_count, int context_size)
{
if (context_size <= 0)
return true;
return eval_count + prompt_eval_count <= context_size;
}
}

View File

@@ -31,6 +31,6 @@ namespace AICore.Facade.LLM
}
public abstract ApiResponse<IEnumerable<AiModelDC>> GetAiModelle();
public abstract ApiResponse<string> SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null);
public abstract ApiResponse<string> SendAiMessageRequest(dynamic payload, out LLMResponse llm_response, FileLogger logger = null);
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AICore.Facade.LLM
{
public class LLMResponse
{
public string Message { get; set; }
public string Model { get; set; }
public int? Duration { get; set; }
public int? PromptEvalCount { get; set; }
public int? EvalCount { get; set; }
public LLMResponse() { }
public LLMResponse(string message, string model, int? duration, int? promptEvalCount, int? evalCount)
{
Message = message;
Model = model;
Duration = duration;
PromptEvalCount = promptEvalCount;
EvalCount = evalCount;
}
}
}

View File

@@ -71,18 +71,9 @@ 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, FileLogger logger = null)
public override ApiResponse<string> SendAiMessageRequest(dynamic payload, out LLMResponse llm_response, FileLogger logger = null)
{
return SendAiMessageRequest((object)payload, out message, out model, out duration, out long? _, out long? _, logger);
}
public ApiResponse<string> SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, out long? prompt_eval_count, out long? eval_count, FileLogger logger = null)
{
message = null;
duration = null;
model = null;
prompt_eval_count = null;
eval_count = null;
llm_response = null;
_PostClientFacade.JsonObject = payload;
@@ -130,20 +121,21 @@ namespace AICore.Facade.LLM
var json = response.GetResponseData();
prompt_eval_count = json.prompt_eval_count;
eval_count = json.eval_count;
llm_response = new LLMResponse();
llm_response.PromptEvalCount = (int)json.prompt_eval_count;
llm_response.EvalCount = (int)json.eval_count;
if (json.message is object && !string.IsNullOrEmpty(json.message.content))
{
var total_ns = json.total_duration;
var total_ms = total_ns / 1000000;
duration = (int)total_ms;
model = json.model;
llm_response.Duration = (int)total_ms;
llm_response.Model = json.model;
var assi_msg = BS.Shared.Core.Utils.ConvertToISO88591(json.message.content);
message = assi_msg;
llm_response.Message = assi_msg;
return ApiResponse<string>.SuccessResponse(assi_msg);
}

View File

@@ -53,11 +53,9 @@ 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, FileLogger logger = null)
public override ApiResponse<string> SendAiMessageRequest(dynamic payload, out LLMResponse llm_response, FileLogger logger = null)
{
message = null;
duration = null;
model = null;
llm_response = null;
_PostClientFacade.JsonObject = payload;
@@ -110,12 +108,14 @@ namespace AICore.Facade.LLM
var total_ns = json.usage.total_duration;
var total_ms = total_ns / 1000000;
duration = (int)total_ms;
model = json.model;
llm_response = new LLMResponse();
llm_response.Duration = (int)total_ms;
llm_response.Model = json.model;
var assi_msg = BS.Shared.Core.Utils.ConvertToISO88591(choice.message.content);
message = assi_msg;
llm_response.Message = assi_msg;
return ApiResponse<string>.SuccessResponse(assi_msg);
}

View File

@@ -375,7 +375,7 @@ namespace BeWo.Service.Plugins
var modell_oid = config.SelectedModel.Oid.Value;
var current_user = UserRightHelper.GetLoggedInUserWithOid();
var conv = new AiConversation();
conv.Created = DateTime.Now;
@@ -401,7 +401,7 @@ namespace BeWo.Service.Plugins
var context_Type = config.Context_Type;
var sampleCount = prompt?.SamplesCount;
if(prompt is object && !prompt.IsOwnsoftPrompt)
if (prompt is object && !prompt.IsOwnsoftPrompt)
{
sampleCount = null;
}
@@ -426,14 +426,14 @@ namespace BeWo.Service.Plugins
{
CheckPermissions(conv.ActionType);
IAiModel configModel = conv.Modell;
if(configModel == null)
IAiModel configModel = conv.Modell;
if (configModel == null)
{
configModel = GetAiConfig().SelectedModel;
}
var configModelValue = configModel.Value;
var configModelName = configModel.ModelName;
var configMaxContextWindow = configModel.Value;
var logger = ServiceLogger.GetRequestLogger(out string id);
// Updated
@@ -481,21 +481,17 @@ namespace BeWo.Service.Plugins
requestid = id
};
// Check Token Size
if (-1 < configModel.Value)
int estimated_tokens = 0;
var mode = conv.Messages.Count <= 2 ? ContextSizeAnalyzeMode.FirstMessage : ContextSizeAnalyzeMode.ConversationMessage;
var success = ContextSizeAnalyzer.CheckContextSize(messages2, configMaxContextWindow, mode, out estimated_tokens);
if (!success)
{
int token_count = 0;
var error = $"send error: windowsize exceeded ({estimated_tokens}/{configMaxContextWindow} (mode:{mode}))";
var mode = conv.Messages.Count <= 2 ? ContextSizeAnalyzeMode.FirstMessage : ContextSizeAnalyzeMode.ConversationMessage;
var success = ContextSizeAnalyzer.CheckContextSize(messages2, configModel.Value, mode, out token_count);
if (!success)
{
var error = $"Windowsize exceeded: {token_count}/{configModel.Value} (mode:{mode})";
throw new BeWoNotImplementedException(error);
}
throw new BeWoInvalidOperationException(error);
}
// Update DB Entity
@@ -509,24 +505,38 @@ namespace BeWo.Service.Plugins
var client = ServiceFacade.GetLLMApiClientByConfig(configModel.ModelSource);
var response = client.SendAiMessageRequest(payload, out var message2, out var model, out var duration, logger);
var response = client.SendAiMessageRequest(payload, out var llm_response, logger);
var result = response.GetResponseData();
if (llm_response.PromptEvalCount is int prompt_eval_count &&
llm_response.EvalCount is int eval_count)
{
if (ContextSizeAnalyzer.CheckForTruncation(estimated_tokens, prompt_eval_count))
{
throw new BeWoInvalidOperationException($"send error: truncation ({estimated_tokens}/{llm_response.PromptEvalCount.Value})");
}
if (!ContextSizeAnalyzer.CheckForValidAssistent(eval_count, prompt_eval_count, configMaxContextWindow))
{
throw new BeWoInvalidOperationException($"send error: assist check failed: ({eval_count}/{prompt_eval_count}/{configMaxContextWindow})");
}
}
// Antwort
var assi_msg = new AiConversationMessage();
assi_msg.Message = message2;
assi_msg.Message = llm_response.Message;
assi_msg.AiConversation = conv;
assi_msg.Role = BS.Shared.AiConversationMessageRole.Assistent;
assi_msg.Created = dt_before_request;
assi_msg.Duration = duration ?? 0;
assi_msg.ModelName = model;
assi_msg.Duration = llm_response.Duration ?? 0;
assi_msg.ModelName = llm_response.Model;
assi_msg.ShowMessage = true;
conv.Messages.Add(assi_msg);
// Update conv.
if(save)
if (save)
DAOFactory.GenericDAO.Update(conv);
var assi_msg_dc = MapperFactory.AiConversationMessage.MapToNewDC(assi_msg);

View File

@@ -1,3 +1,11 @@
using AICore.Facade.LLM;
using BeWo.Data.Access;
using BeWo.Data.Entities;
using BeWo.Data.Security;
using BeWo.Service.Core;
using BeWo.Service.ServiceProxy;
using BS.Shared.Tests.Objects;
namespace BeWo.Service.Status
{
public class AiStatusService : BaseStatusService
@@ -16,6 +24,55 @@ namespace BeWo.Service.Status
"Changes_2026-07-08 AiConversation Promptreferenz",
"Changes_2026-07-10 AiConversationMessage ShowMessage",
"Changes_2026-07-22 AiConversationMessage UsedPromptbausteinTitle");
RegisterCheckup(CheckEndpointOneGetModels,
CheckEndpointOneSendMessage,
CheckEndpointTwoGetModels,
CheckEndpointTwoSendMessage);
}
private string CheckEndpointOneGetModels()
{
var response = ServiceFacade.OllamaApiClient.GetAiModelle();
if (!response.Success)
return "endpoint 1m: " + response.ToErrorString();
return null;
}
private string CheckEndpointOneSendMessage()
{
var logger = ServiceLogger.GetRequestLogger(out string id);
var payload = AiTestObjects.GetExampleSendRequest(UserRightHelper.GetTenant(), id);
var response = ServiceFacade.OllamaApiClient.SendAiMessageRequest(payload, out LLMResponse llm_response, logger);
if (!response.Success)
return "endpoint 1s: " + response.ToErrorString();
return null;
}
private string CheckEndpointTwoGetModels()
{
var response = ServiceFacade.Ollama2ApiClient.GetAiModelle();
if (!response.Success)
return "endpoint 2m: " + response.ToErrorString();
return null;
}
private string CheckEndpointTwoSendMessage()
{
var logger = ServiceLogger.GetRequestLogger(out string id);
var payload = AiTestObjects.GetExampleSendRequest(UserRightHelper.GetTenant(), id);
var response = ServiceFacade.Ollama2ApiClient.SendAiMessageRequest(payload, out LLMResponse llm_response, logger);
if (!response.Success)
return "endpoint 2s: " + response.ToErrorString();
return null;
}
}
}

View File

@@ -14,6 +14,10 @@ namespace BS.Shared.Exceptions
}
public BeWoInvalidOperationException(string message) : base(message)
{
}
public BeWoInvalidOperationException(AppError appError) : base(appError.Displayname)
{

View File

@@ -601,6 +601,7 @@
<Compile Include="Extensions\UIElementCollectionExtensions.cs" />
<Compile Include="Services\SupportConceptService.cs" />
<Compile Include="Settings\ApplicationSettings.cs" />
<Compile Include="Tests\Objects\AiTestObjects.cs" />
<Compile Include="Text\AiPromptbausteineTexts.cs" />
<Compile Include="Translation\ITranslator.cs" />
<Compile Include="Translation\Translator.cs" />

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BS.Shared.Tests.Objects
{
public static class AiTestObjects
{
public static dynamic GetExampleSendRequest(string customerid, string request_id)
{
var payload = new
{
model = "gemma3:12b",
messages = new[] {
new {
role = "user",
message = "Say 'Hi'!"
}
},
customerid = customerid,
requestid = request_id
};
return payload;
}
}
}