Zusätzliche Checkups eingebunden
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
28
Server/Components/AICore/Facade/LLM/LLMResponse.cs
Normal file
28
Server/Components/AICore/Facade/LLM/LLMResponse.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace BS.Shared.Exceptions
|
||||
|
||||
}
|
||||
|
||||
public BeWoInvalidOperationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public BeWoInvalidOperationException(AppError appError) : base(appError.Displayname)
|
||||
{
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
29
Shared/Tests/Objects/AiTestObjects.cs
Normal file
29
Shared/Tests/Objects/AiTestObjects.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user