diff --git a/AiCoreUnitTest/Ollama2FacadeTest.cs b/AiCoreUnitTest/Ollama2FacadeTest.cs
index a5fa2d403..7a59ec042 100644
--- a/AiCoreUnitTest/Ollama2FacadeTest.cs
+++ b/AiCoreUnitTest/Ollama2FacadeTest.cs
@@ -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);
}
diff --git a/AiCoreUnitTest/OllamaFacadeTest.cs b/AiCoreUnitTest/OllamaFacadeTest.cs
index 2eeaf6d36..ce718abab 100644
--- a/AiCoreUnitTest/OllamaFacadeTest.cs
+++ b/AiCoreUnitTest/OllamaFacadeTest.cs
@@ -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);
}
diff --git a/Server/Components/AICore/AICore.csproj b/Server/Components/AICore/AICore.csproj
index e4e2f1e57..1dcd070fb 100644
--- a/Server/Components/AICore/AICore.csproj
+++ b/Server/Components/AICore/AICore.csproj
@@ -112,6 +112,7 @@
+
diff --git a/Server/Components/AICore/Core/ContextSizeAnalyzer.cs b/Server/Components/AICore/Core/ContextSizeAnalyzer.cs
index 65603b766..c4825f73a 100644
--- a/Server/Components/AICore/Core/ContextSizeAnalyzer.cs
+++ b/Server/Components/AICore/Core/ContextSizeAnalyzer.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;
}
}
diff --git a/Server/Components/AICore/Facade/LLM/BaseLLMApiClient.cs b/Server/Components/AICore/Facade/LLM/BaseLLMApiClient.cs
index 6b81f50c2..ec645c468 100644
--- a/Server/Components/AICore/Facade/LLM/BaseLLMApiClient.cs
+++ b/Server/Components/AICore/Facade/LLM/BaseLLMApiClient.cs
@@ -31,6 +31,6 @@ namespace AICore.Facade.LLM
}
public abstract ApiResponse> GetAiModelle();
- public abstract ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null);
+ public abstract ApiResponse SendAiMessageRequest(dynamic payload, out LLMResponse llm_response, FileLogger logger = null);
}
}
diff --git a/Server/Components/AICore/Facade/LLM/LLMResponse.cs b/Server/Components/AICore/Facade/LLM/LLMResponse.cs
new file mode 100644
index 000000000..dbe28d0e1
--- /dev/null
+++ b/Server/Components/AICore/Facade/LLM/LLMResponse.cs
@@ -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;
+ }
+ }
+}
diff --git a/Server/Components/AICore/Facade/LLM/OllamaApiClient.cs b/Server/Components/AICore/Facade/LLM/OllamaApiClient.cs
index 8325f843d..6a5fca01a 100644
--- a/Server/Components/AICore/Facade/LLM/OllamaApiClient.cs
+++ b/Server/Components/AICore/Facade/LLM/OllamaApiClient.cs
@@ -71,18 +71,9 @@ namespace AICore.Facade.LLM
return ApiResponse>.SuccessResponse(data);
}
- public override ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null)
+ public override ApiResponse 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 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.SuccessResponse(assi_msg);
}
diff --git a/Server/Components/AICore/Facade/LLM/OpenWebApiClient.cs b/Server/Components/AICore/Facade/LLM/OpenWebApiClient.cs
index 81fe5872f..8a93af667 100644
--- a/Server/Components/AICore/Facade/LLM/OpenWebApiClient.cs
+++ b/Server/Components/AICore/Facade/LLM/OpenWebApiClient.cs
@@ -53,11 +53,9 @@ namespace AICore.Facade.LLM
return ApiResponse>.SuccessResponse(data);
}
- public override ApiResponse SendAiMessageRequest(dynamic payload, out string message, out string model, out int? duration, FileLogger logger = null)
+ public override ApiResponse 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.SuccessResponse(assi_msg);
}
diff --git a/Service/Plugins/AiService2.cs b/Service/Plugins/AiService2.cs
index 5dc4bc2a9..ff6c672ae 100644
--- a/Service/Plugins/AiService2.cs
+++ b/Service/Plugins/AiService2.cs
@@ -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);
diff --git a/Service/Status/AiStatusService.cs b/Service/Status/AiStatusService.cs
index dbf33d326..1433bf32f 100644
--- a/Service/Status/AiStatusService.cs
+++ b/Service/Status/AiStatusService.cs
@@ -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;
}
}
}
diff --git a/Shared/Exceptions/BeWoInvalidOperationException.cs b/Shared/Exceptions/BeWoInvalidOperationException.cs
index 05a798132..24574d7a5 100644
--- a/Shared/Exceptions/BeWoInvalidOperationException.cs
+++ b/Shared/Exceptions/BeWoInvalidOperationException.cs
@@ -14,6 +14,10 @@ namespace BS.Shared.Exceptions
}
+ public BeWoInvalidOperationException(string message) : base(message)
+ {
+ }
+
public BeWoInvalidOperationException(AppError appError) : base(appError.Displayname)
{
diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj
index 5e9b71b4b..9cf6c64f4 100644
--- a/Shared/Shared.csproj
+++ b/Shared/Shared.csproj
@@ -601,6 +601,7 @@
+
diff --git a/Shared/Tests/Objects/AiTestObjects.cs b/Shared/Tests/Objects/AiTestObjects.cs
new file mode 100644
index 000000000..9a178d920
--- /dev/null
+++ b/Shared/Tests/Objects/AiTestObjects.cs
@@ -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;
+ }
+ }
+}