From 9e568911330cd8c1670eef01df3561820131cc97 Mon Sep 17 00:00:00 2001 From: Szymon Date: Mon, 21 Jul 2025 23:04:37 +0200 Subject: [PATCH 1/9] [#19] Add first part of custom exceptions --- .../Examples/Chat/ChatFromExistingExample.cs | 15 ++++++++++--- src/MaIN.Core/Hub/Contexts/ChatContext.cs | 22 ++++++++++++------- .../AgentContextNotFoundException.cs | 9 ++++++++ .../Exceptions/AgentNotFoundException.cs | 10 +++++++++ .../Exceptions/ChatNotFoundException.cs | 10 +++++++++ .../Exceptions/ChatNotInitializedException.cs | 9 ++++++++ .../Exceptions/EmptyChatException.cs | 9 ++++++++ .../Exceptions/MaINCustomException.cs | 10 +++++++++ .../Exceptions/ModelNotSupportedException.cs | 10 +++++++++ src/MaIN.Domain/Models/SupportedModels.cs | 8 +++---- src/MaIN.Services/Services/AgentService.cs | 18 ++++++++++----- src/MaIN.Services/Services/ChatService.cs | 8 ++++++- 12 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 src/MaIN.Domain/Exceptions/AgentContextNotFoundException.cs create mode 100644 src/MaIN.Domain/Exceptions/AgentNotFoundException.cs create mode 100644 src/MaIN.Domain/Exceptions/ChatNotFoundException.cs create mode 100644 src/MaIN.Domain/Exceptions/ChatNotInitializedException.cs create mode 100644 src/MaIN.Domain/Exceptions/EmptyChatException.cs create mode 100644 src/MaIN.Domain/Exceptions/MaINCustomException.cs create mode 100644 src/MaIN.Domain/Exceptions/ModelNotSupportedException.cs diff --git a/Examples/Examples/Chat/ChatFromExistingExample.cs b/Examples/Examples/Chat/ChatFromExistingExample.cs index ce123f21..6b77ac7e 100644 --- a/Examples/Examples/Chat/ChatFromExistingExample.cs +++ b/Examples/Examples/Chat/ChatFromExistingExample.cs @@ -1,5 +1,6 @@ using System.Text.Json; using MaIN.Core.Hub; +using MaIN.Domain.Exceptions; namespace Examples; @@ -18,8 +19,16 @@ public async Task Start() await result.WithMessage("And about physics?") .CompleteAsync(); - var chatNewContext = await AIHub.Chat().FromExisting(result.GetChatId()); - var messages = chatNewContext.GetChatHistory(); - Console.WriteLine(JsonSerializer.Serialize(messages)); + try + { + var chatNewContext = await AIHub.Chat().FromExisting(result.GetChatId()); + var messages = chatNewContext.GetChatHistory(); + Console.WriteLine(JsonSerializer.Serialize(messages)); + } + catch (ChatNotFoundException ex) + { + Console.WriteLine(ex.PublicErrorMessage); + } + } } \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/ChatContext.cs b/src/MaIN.Core/Hub/Contexts/ChatContext.cs index 8e7dd25b..967cc8af 100644 --- a/src/MaIN.Core/Hub/Contexts/ChatContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ChatContext.cs @@ -1,5 +1,6 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Domain.Models; using MaIN.Services; using MaIN.Services.Constants; @@ -169,8 +170,9 @@ public async Task CompleteAsync( { if (_chat.Messages.Count == 0) { - throw new InvalidOperationException("Chat has no messages."); //TODO good candidate for domain exception + throw new EmptyChatException(_chat.Id); } + _chat.Messages.Last().Files = _files; if(_preProcess) { @@ -190,7 +192,9 @@ public async Task CompleteAsync( public async Task GetCurrentChat() { if (_chat.Id == null) - throw new InvalidOperationException("Chat has not been created yet. Call CompleteAsync first."); + { + throw new ChatNotInitializedException(); + } return await _chatService.GetById(_chat.Id); } @@ -203,8 +207,10 @@ public async Task> GetAllChats() public async Task DeleteChat() { if (_chat.Id == null) - throw new InvalidOperationException("Chat has not been created yet."); - + { + throw new ChatNotInitializedException(); + } + await _chatService.Delete(_chat.Id); } @@ -225,10 +231,10 @@ private async Task ChatExists(string id) public async Task FromExisting(string chatId) { var existingChat = await _chatService.GetById(chatId); - if (existingChat == null) - { - throw new Exception("Chat not found"); - } + // if (existingChat == null) + // { + // throw new Exception("Chat not found"); + // } return new ChatContext(_chatService, existingChat); } diff --git a/src/MaIN.Domain/Exceptions/AgentContextNotFoundException.cs b/src/MaIN.Domain/Exceptions/AgentContextNotFoundException.cs new file mode 100644 index 00000000..0126e2e1 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/AgentContextNotFoundException.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class AgentContextNotFoundException(string agentId) : MaINCustomException($"Context of agent with id: '{agentId}' not found.") +{ + public override string PublicErrorMessage => "Agent context not found."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.NotFound; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/AgentNotFoundException.cs b/src/MaIN.Domain/Exceptions/AgentNotFoundException.cs new file mode 100644 index 00000000..7a2556dc --- /dev/null +++ b/src/MaIN.Domain/Exceptions/AgentNotFoundException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class AgentNotFoundException(string agentId) + : MaINCustomException($"Agent with id: '{agentId}' not found.") +{ + public override string PublicErrorMessage => "Agent not found."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.NotFound; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/ChatNotFoundException.cs b/src/MaIN.Domain/Exceptions/ChatNotFoundException.cs new file mode 100644 index 00000000..e01d3018 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/ChatNotFoundException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class ChatNotFoundException(string chatId) + : MaINCustomException($"Chat with id: '{chatId}' not found.") +{ + public override string PublicErrorMessage => "Chat not found."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.NotFound; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/ChatNotInitializedException.cs b/src/MaIN.Domain/Exceptions/ChatNotInitializedException.cs new file mode 100644 index 00000000..4750eb90 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/ChatNotInitializedException.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class ChatNotInitializedException() : MaINCustomException("Chat has not been created yet. Call 'CompleteAsync' operation first.") +{ + public override string PublicErrorMessage => Message; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.Conflict; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/EmptyChatException.cs b/src/MaIN.Domain/Exceptions/EmptyChatException.cs new file mode 100644 index 00000000..5157132b --- /dev/null +++ b/src/MaIN.Domain/Exceptions/EmptyChatException.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class EmptyChatException(string chatId) : MaINCustomException($"Chat with id: '{chatId}' is empty. Complete operation is impossible.") +{ + public override string PublicErrorMessage => "Complete operation is impossible, because chat has no message."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.Conflict; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/MaINCustomException.cs b/src/MaIN.Domain/Exceptions/MaINCustomException.cs new file mode 100644 index 00000000..71084323 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/MaINCustomException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public abstract class MaINCustomException(string message) : Exception(message) +{ + public string LogMessage { get; private set; } = message; + public abstract string PublicErrorMessage { get; } + public abstract HttpStatusCode HttpStatusCode { get; } +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/ModelNotSupportedException.cs b/src/MaIN.Domain/Exceptions/ModelNotSupportedException.cs new file mode 100644 index 00000000..4ac460e8 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/ModelNotSupportedException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class ModelNotSupportedException(string? modelName) + : MaINCustomException($"Given model {modelName ?? string.Empty} is not supported.") +{ + public override string PublicErrorMessage => Message; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.BadRequest; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Models/SupportedModels.cs b/src/MaIN.Domain/Models/SupportedModels.cs index 61354f53..dfe0703f 100644 --- a/src/MaIN.Domain/Models/SupportedModels.cs +++ b/src/MaIN.Domain/Models/SupportedModels.cs @@ -1,3 +1,5 @@ +using MaIN.Domain.Exceptions; + namespace MaIN.Domain.Models; public class Model @@ -202,8 +204,7 @@ public static Model GetModel(string path, string? name) StringComparison.InvariantCultureIgnoreCase)); if (model is null) { - //todo support domain specific exceptions - throw new Exception($"Model {name} is not supported"); + throw new ModelNotSupportedException(name); } if (File.Exists(Path.Combine(path, model.FileName))) @@ -252,8 +253,7 @@ public static Model GetModel(string modelName) StringComparison.InvariantCultureIgnoreCase)); if (model is null) { - //todo support domain specific exceptions - throw new NotSupportedException($"Model {modelName} is not supported"); + throw new ModelNotSupportedException(modelName); } return model; diff --git a/src/MaIN.Services/Services/AgentService.cs b/src/MaIN.Services/Services/AgentService.cs index d0c0827e..34f95f7b 100644 --- a/src/MaIN.Services/Services/AgentService.cs +++ b/src/MaIN.Services/Services/AgentService.cs @@ -1,6 +1,7 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; +using MaIN.Domain.Exceptions; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Constants; using MaIN.Services.Mappers; @@ -28,10 +29,15 @@ public class AgentService( public async Task Process(Chat chat, string agentId, bool translatePrompt = false) { var agent = await agentRepository.GetAgentById(agentId); - if (agent == null) - throw new ArgumentException("Agent not found."); //TODO candidate for NotFound domain exception - if (agent.Context == null) - throw new ArgumentException("Agent context not found."); + if (agent == null) + { + throw new AgentNotFoundException(agentId); + } + + if (agent.Context == null) + { + throw new AgentContextNotFoundException(agentId); + } await notificationService.DispatchNotification( NotificationMessageBuilder.ProcessingStarted(agentId, agent.CurrentBehaviour), "ReceiveAgentUpdate"); @@ -115,7 +121,9 @@ public async Task GetChatByAgent(string agentId) { var agent = await agentRepository.GetAgentById(agentId); if (agent == null) - throw new Exception("Agent not found."); //TODO good candidate for custom exception + { + throw new AgentNotFoundException(agentId); + } var chat = await chatRepository.GetChatById(agent.ChatId); return chat!.ToDomain(); diff --git a/src/MaIN.Services/Services/ChatService.cs b/src/MaIN.Services/Services/ChatService.cs index 20079b2d..6e5dce7e 100644 --- a/src/MaIN.Services/Services/ChatService.cs +++ b/src/MaIN.Services/Services/ChatService.cs @@ -1,5 +1,6 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Domain.Models; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Mappers; @@ -89,7 +90,12 @@ public async Task Delete(string id) public async Task GetById(string id) { var chatDocument = await chatProvider.GetChatById(id); - if(chatDocument == null) throw new Exception("Chat not found"); //TODO good candidate for custom exception + if (chatDocument == null) + { + throw new ChatNotFoundException(id); + // throw new Exception("Chat not found"); + //TODO good candidate for custom exception + } return chatDocument.ToDomain(); } From 7979825be5d9804ac72c658bcb3e5ee0d2adb8bc Mon Sep 17 00:00:00 2001 From: Szymon Date: Thu, 24 Jul 2025 16:13:07 +0200 Subject: [PATCH 2/9] [#19] Add other custom exceptions --- .../Exceptions/ApiRequestFailedException.cs | 10 ++++++++++ src/MaIN.Domain/Exceptions/CommandFailedException.cs | 10 ++++++++++ src/MaIN.Domain/Models/SupportedModels.cs | 3 +-- src/MaIN.Services/Services/AgentService.cs | 4 +++- src/MaIN.Services/Services/ChatService.cs | 3 +-- src/MaIN.Services/Services/DataSourceProvider.cs | 7 +++++-- src/MaIN.Services/Services/Steps/AnswerStepHandler.cs | 8 +++++--- .../Services/Steps/FechDataStepHandler.cs | 3 ++- src/MaIN.Services/Services/Steps/McpStepHandler.cs | 3 ++- 9 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 src/MaIN.Domain/Exceptions/ApiRequestFailedException.cs create mode 100644 src/MaIN.Domain/Exceptions/CommandFailedException.cs diff --git a/src/MaIN.Domain/Exceptions/ApiRequestFailedException.cs b/src/MaIN.Domain/Exceptions/ApiRequestFailedException.cs new file mode 100644 index 00000000..903136ba --- /dev/null +++ b/src/MaIN.Domain/Exceptions/ApiRequestFailedException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class ApiRequestFailedException(HttpStatusCode statusCode, string requestUrl, string httpMethod) + : MaINCustomException($"API request failed with status code: {statusCode}. Request url: {requestUrl}. Http method: {httpMethod}.") +{ + public override string PublicErrorMessage => "An error occurred while processing an external API request"; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.InternalServerError; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/CommandFailedException.cs b/src/MaIN.Domain/Exceptions/CommandFailedException.cs new file mode 100644 index 00000000..25ce01e7 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/CommandFailedException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class CommandFailedException(string commandName) + : MaINCustomException($"{commandName} command execution failed.") +{ + public override string PublicErrorMessage => Message; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.InternalServerError; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Models/SupportedModels.cs b/src/MaIN.Domain/Models/SupportedModels.cs index dfe0703f..cfb09cb3 100644 --- a/src/MaIN.Domain/Models/SupportedModels.cs +++ b/src/MaIN.Domain/Models/SupportedModels.cs @@ -220,8 +220,7 @@ public static Model GetModel(string path, string? name) var isPresent = Models.Exists(x => x.FileName == fileName); if (!isPresent) { - //todo support domain specific exceptions - Console.WriteLine($"Model {fileName} is not supported"); + Console.WriteLine($"{new ModelNotSupportedException(fileName).PublicErrorMessage}"); return null; } diff --git a/src/MaIN.Services/Services/AgentService.cs b/src/MaIN.Services/Services/AgentService.cs index 34f95f7b..6c991b5d 100644 --- a/src/MaIN.Services/Services/AgentService.cs +++ b/src/MaIN.Services/Services/AgentService.cs @@ -133,7 +133,9 @@ public async Task Restart(string agentId) { var agent = await agentRepository.GetAgentById(agentId); if (agent == null) - throw new Exception("Agent not found."); //TODO good candidate for custom exception + { + throw new AgentNotFoundException(agentId); + } var chat = (await chatRepository.GetChatById(agent.ChatId))!.ToDomain(); var llmService = llmServiceFactory.CreateService(agent.Backend ?? maInSettings.BackendType); diff --git a/src/MaIN.Services/Services/ChatService.cs b/src/MaIN.Services/Services/ChatService.cs index 6e5dce7e..c40cbaed 100644 --- a/src/MaIN.Services/Services/ChatService.cs +++ b/src/MaIN.Services/Services/ChatService.cs @@ -93,9 +93,8 @@ public async Task GetById(string id) if (chatDocument == null) { throw new ChatNotFoundException(id); - // throw new Exception("Chat not found"); - //TODO good candidate for custom exception } + return chatDocument.ToDomain(); } diff --git a/src/MaIN.Services/Services/DataSourceProvider.cs b/src/MaIN.Services/Services/DataSourceProvider.cs index e92c5b34..b9bbd793 100644 --- a/src/MaIN.Services/Services/DataSourceProvider.cs +++ b/src/MaIN.Services/Services/DataSourceProvider.cs @@ -2,6 +2,7 @@ using System.Text; using System.Text.Json; using MaIN.Domain.Entities.Agents.AgentSource; +using MaIN.Domain.Exceptions; using MaIN.Services.Services.Abstract; using MaIN.Services.Utils; using MongoDB.Bson; @@ -76,8 +77,10 @@ public async Task FetchApiData(object? details, string? filter, var result = await httpClient.SendAsync(request); if (!result.IsSuccessStatusCode) { - throw new Exception( - $"API request failed with status code: {result.StatusCode}"); //TODO candidate for domain exception + throw new ApiRequestFailedException( + result.StatusCode, + apiDetails?.Url + apiDetails?.Query, + apiDetails?.Method ?? string.Empty); } var data = await result.Content.ReadAsStringAsync(); diff --git a/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs b/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs index a2fc7f85..18dc4aa3 100644 --- a/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs @@ -1,6 +1,7 @@ using System.Text.RegularExpressions; using DocumentFormat.OpenXml.Wordprocessing; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; @@ -24,9 +25,10 @@ public async Task Handle(StepContext context) }; var answerResponse = await commandDispatcher.DispatchAsync(answerCommand); - if (answerResponse == null) - throw new Exception("Answer command failed"); //TODO proper candidate for custom exception - + if (answerResponse == null) + { + throw new CommandFailedException(answerCommand.CommandName); + } var filterVal = GetFilter(answerResponse.Content); if (!string.IsNullOrEmpty(filterVal)) diff --git a/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs b/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs index 6c70818e..f8745d32 100644 --- a/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs @@ -1,5 +1,6 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Services.Mappers; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; @@ -35,7 +36,7 @@ public async Task Handle(StepContext context) var response = await commandDispatcher.DispatchAsync(fetchCommand); if (response == null) { - throw new InvalidOperationException("Data fetch command failed"); //TODO proper candidate for custom exception + throw new CommandFailedException(fetchCommand.CommandName); } if (context.StepName == "FETCH_DATA*") diff --git a/src/MaIN.Services/Services/Steps/McpStepHandler.cs b/src/MaIN.Services/Services/Steps/McpStepHandler.cs index b3cb6860..9c501a72 100644 --- a/src/MaIN.Services/Services/Steps/McpStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/McpStepHandler.cs @@ -1,6 +1,7 @@ using System.Text.RegularExpressions; using DocumentFormat.OpenXml.Wordprocessing; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; @@ -29,7 +30,7 @@ public async Task Handle(StepContext context) var mcpResponse = await commandDispatcher.DispatchAsync(mcpCommand); if (mcpResponse == null) { - throw new Exception("MCP command failed"); //TODO proper candidate for custom exception + throw new CommandFailedException(mcpCommand.CommandName); } var filterVal = GetFilter(mcpResponse.Content); From d1e4e62f69a751ffaaf2796309c8fcbaa825b630 Mon Sep 17 00:00:00 2001 From: Szymon Date: Thu, 24 Jul 2025 20:58:42 +0200 Subject: [PATCH 3/9] Add some improvements --- src/MaIN.Core/Hub/Contexts/ChatContext.cs | 8 ++++---- .../Exceptions/AgentFlowNotFoundException.cs | 9 +++++++++ src/MaIN.Domain/Exceptions/MaINCustomException.cs | 11 +++++++++++ .../Exceptions/ModelNotDownloadedException.cs | 10 ++++++++++ src/MaIN.Domain/Models/SupportedModels.cs | 4 ++-- 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 src/MaIN.Domain/Exceptions/AgentFlowNotFoundException.cs create mode 100644 src/MaIN.Domain/Exceptions/ModelNotDownloadedException.cs diff --git a/src/MaIN.Core/Hub/Contexts/ChatContext.cs b/src/MaIN.Core/Hub/Contexts/ChatContext.cs index 967cc8af..b4a60269 100644 --- a/src/MaIN.Core/Hub/Contexts/ChatContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ChatContext.cs @@ -231,10 +231,10 @@ private async Task ChatExists(string id) public async Task FromExisting(string chatId) { var existingChat = await _chatService.GetById(chatId); - // if (existingChat == null) - // { - // throw new Exception("Chat not found"); - // } + if (existingChat == null) + { + throw new ChatNotFoundException(chatId); + } return new ChatContext(_chatService, existingChat); } diff --git a/src/MaIN.Domain/Exceptions/AgentFlowNotFoundException.cs b/src/MaIN.Domain/Exceptions/AgentFlowNotFoundException.cs new file mode 100644 index 00000000..bb271815 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/AgentFlowNotFoundException.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class AgentFlowNotFoundException(string flowId) : MaINCustomException($"Agent flow with id: '{flowId}' not found.") +{ + public override string PublicErrorMessage => "Agent flow not found."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.NotFound; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/MaINCustomException.cs b/src/MaIN.Domain/Exceptions/MaINCustomException.cs index 71084323..2cf50841 100644 --- a/src/MaIN.Domain/Exceptions/MaINCustomException.cs +++ b/src/MaIN.Domain/Exceptions/MaINCustomException.cs @@ -1,10 +1,21 @@ using System.Net; +using System.Text.RegularExpressions; namespace MaIN.Domain.Exceptions; public abstract class MaINCustomException(string message) : Exception(message) { + public string ErrorCode => GenerateErrorCode(); public string LogMessage { get; private set; } = message; public abstract string PublicErrorMessage { get; } public abstract HttpStatusCode HttpStatusCode { get; } + + private string GenerateErrorCode() + { + var typeName = GetType().Name; + var snakeCaseTypeName = Regex.Replace(typeName, "(? Message; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.NotFound; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Models/SupportedModels.cs b/src/MaIN.Domain/Models/SupportedModels.cs index cfb09cb3..b0459ade 100644 --- a/src/MaIN.Domain/Models/SupportedModels.cs +++ b/src/MaIN.Domain/Models/SupportedModels.cs @@ -212,7 +212,7 @@ public static Model GetModel(string path, string? name) return model; } - throw new Exception($"Model {name} is not downloaded"); + throw new ModelNotDownloadedException(name); } public static Model? GetModelByFileName(string path, string fileName) @@ -229,7 +229,7 @@ public static Model GetModel(string path, string? name) return Models.First(x => x.FileName == fileName); } - throw new Exception($"Model {fileName} is not downloaded"); + throw new ModelNotDownloadedException(fileName); } public static void AddModel(string model, string path, string? mmProject = null) From d45740d2e00efabc87cb9ce5423bcfbcdf746b9c Mon Sep 17 00:00:00 2001 From: Szymon Date: Thu, 24 Jul 2025 21:01:35 +0200 Subject: [PATCH 4/9] Tweaks --- src/MaIN.Domain/Exceptions/MaINCustomException.cs | 1 - src/MaIN.Services/Services/AgentFlowService.cs | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/MaIN.Domain/Exceptions/MaINCustomException.cs b/src/MaIN.Domain/Exceptions/MaINCustomException.cs index 2cf50841..2530f8be 100644 --- a/src/MaIN.Domain/Exceptions/MaINCustomException.cs +++ b/src/MaIN.Domain/Exceptions/MaINCustomException.cs @@ -17,5 +17,4 @@ private string GenerateErrorCode() return snakeCaseTypeName.Replace("_exception", string.Empty); } - } \ No newline at end of file diff --git a/src/MaIN.Services/Services/AgentFlowService.cs b/src/MaIN.Services/Services/AgentFlowService.cs index 762a1663..1ab7e8ce 100644 --- a/src/MaIN.Services/Services/AgentFlowService.cs +++ b/src/MaIN.Services/Services/AgentFlowService.cs @@ -1,4 +1,5 @@ using MaIN.Domain.Entities.Agents.AgentSource; +using MaIN.Domain.Exceptions; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Mappers; @@ -11,8 +12,10 @@ public class AgentFlowService(IAgentFlowRepository flowRepository, IAgentService public async Task GetFlowById(string id) { var flow = await flowRepository.GetFlowById(id); - if(flow is null) - throw new Exception("Flow not found"); + if (flow is null) + { + throw new AgentFlowNotFoundException(id); + } return flow.ToDomain(); } From ee74e72849373a286b4d2c2a34100a1fc8c7ad29 Mon Sep 17 00:00:00 2001 From: Szymon Date: Fri, 2 Jan 2026 22:02:54 +0100 Subject: [PATCH 5/9] [#19] Add another part of custom exceptions, add error handling in examples --- Examples/Examples/Program.cs | 16 +++++++++++++++- src/MaIN.Core/Hub/AiHub.cs | 4 ++-- src/MaIN.Core/Hub/Contexts/AgentContext.cs | 5 ++++- src/MaIN.Core/Hub/Contexts/FlowContext.cs | 12 ++++++------ src/MaIN.Core/Hub/Contexts/McpContext.cs | 3 ++- .../Exceptions/AIHubNotInitializedException.cs | 10 ++++++++++ .../Exceptions/APIKeyNotConfiguredException.cs | 9 +++++++++ .../Exceptions/AgentAlreadyExistsException.cs | 10 ++++++++++ .../Exceptions/ChatAlreadyExistsException.cs | 10 ++++++++++ .../Exceptions/FlowAlreadyExistsException.cs | 10 ++++++++++ src/MaIN.Domain/Exceptions/FlowFoundException.cs | 10 ++++++++++ .../Exceptions/FlowNotInitializedException.cs | 9 +++++++++ .../Exceptions/MPCConfigNotFoundException.cs | 9 +++++++++ .../Repositories/DefaultAgentFlowRepository.cs | 3 ++- .../Repositories/DefaultAgentRepository.cs | 7 ++++--- .../Repositories/DefaultChatRepository.cs | 3 ++- .../FileSystem/FileSystemAgentFlowRepository.cs | 3 ++- .../FileSystem/FileSystemAgentRepository.cs | 3 ++- .../FileSystem/FileSystemChatRepository.cs | 3 ++- .../Services/LLMService/AnthropicService.cs | 5 +++-- .../Services/LLMService/DeepSeekService.cs | 5 +++-- .../Services/LLMService/GroqCloudService.cs | 9 +++++---- .../Services/LLMService/OpenAiService.cs | 7 ++++--- .../Services/Steps/McpStepHandler.cs | 2 +- 24 files changed, 136 insertions(+), 31 deletions(-) create mode 100644 src/MaIN.Domain/Exceptions/AIHubNotInitializedException.cs create mode 100644 src/MaIN.Domain/Exceptions/APIKeyNotConfiguredException.cs create mode 100644 src/MaIN.Domain/Exceptions/AgentAlreadyExistsException.cs create mode 100644 src/MaIN.Domain/Exceptions/ChatAlreadyExistsException.cs create mode 100644 src/MaIN.Domain/Exceptions/FlowAlreadyExistsException.cs create mode 100644 src/MaIN.Domain/Exceptions/FlowFoundException.cs create mode 100644 src/MaIN.Domain/Exceptions/FlowNotInitializedException.cs create mode 100644 src/MaIN.Domain/Exceptions/MPCConfigNotFoundException.cs diff --git a/Examples/Examples/Program.cs b/Examples/Examples/Program.cs index 80cc3d62..a0061284 100644 --- a/Examples/Examples/Program.cs +++ b/Examples/Examples/Program.cs @@ -4,6 +4,7 @@ using Examples.Chat; using Examples.Mcp; using MaIN.Core; +using MaIN.Domain.Exceptions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -121,7 +122,20 @@ async Task RunSelectedExample(IServiceProvider serviceProvider) Console.ResetColor(); var selectedExample = examples[selection - 1].Instance; - await selectedExample.Start(); + try + { + await selectedExample.Start(); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("╔════════════════════════════════════════════════════════════════════╗"); + Console.WriteLine("║ Error ║"); + Console.WriteLine("╚════════════════════════════════════════════════════════════════════╝"); + Console.ResetColor(); + + Console.WriteLine(ex.Message); + } } else { diff --git a/src/MaIN.Core/Hub/AiHub.cs b/src/MaIN.Core/Hub/AiHub.cs index 34cf7c47..1fd2bd14 100644 --- a/src/MaIN.Core/Hub/AiHub.cs +++ b/src/MaIN.Core/Hub/AiHub.cs @@ -2,6 +2,7 @@ using MaIN.Core.Hub.Contexts; using MaIN.Core.Interfaces; using MaIN.Domain.Configuration; +using MaIN.Domain.Exceptions; using MaIN.Services.Services.Abstract; namespace MaIN.Core.Hub; @@ -23,8 +24,7 @@ internal static void Initialize(IAIHubServices services, private static IAIHubServices Services => _services ?? - throw new InvalidOperationException( - "AIHub has not been initialized. Make sure to call AddAIHub() in your service configuration."); + throw new AIHubNotInitializedException(); public static ModelContext Model() => new ModelContext(_settings, _httpClientFactory); public static ChatContext Chat() => new(Services.ChatService); diff --git a/src/MaIN.Core/Hub/Contexts/AgentContext.cs b/src/MaIN.Core/Hub/Contexts/AgentContext.cs index 2fdecb50..519e63ac 100644 --- a/src/MaIN.Core/Hub/Contexts/AgentContext.cs +++ b/src/MaIN.Core/Hub/Contexts/AgentContext.cs @@ -8,6 +8,7 @@ using MaIN.Core.Hub.Utils; using MaIN.Domain.Entities.Agents.Knowledge; using MaIN.Domain.Entities.Tools; +using MaIN.Domain.Exceptions; using MaIN.Services.Constants; namespace MaIN.Core.Hub.Contexts; @@ -341,7 +342,9 @@ public static async Task FromExisting(IAgentService agentService, { var existingAgent = await agentService.GetAgentById(agentId); if (existingAgent == null) - throw new ArgumentException("Agent not found", nameof(agentId)); + { + throw new AgentNotFoundException(agentId); + } var context = new AgentContext(agentService, existingAgent); context.LoadExistingKnowledgeIfExists(); diff --git a/src/MaIN.Core/Hub/Contexts/FlowContext.cs b/src/MaIN.Core/Hub/Contexts/FlowContext.cs index 980bed90..e9baf71b 100644 --- a/src/MaIN.Core/Hub/Contexts/FlowContext.cs +++ b/src/MaIN.Core/Hub/Contexts/FlowContext.cs @@ -4,6 +4,7 @@ using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; using MaIN.Domain.Entities.Agents.AgentSource; +using MaIN.Domain.Exceptions; using MaIN.Services.Dtos; using MaIN.Services.Mappers; using MaIN.Services.Services.Abstract; @@ -210,7 +211,7 @@ public async Task CreateAsync() public async Task Delete() { if (_flow.Id == null) - throw new InvalidOperationException("Flow has not been created yet."); + throw new FlowNotInitializedException(); await _flowService.DeleteFlow(_flow.Id); } @@ -219,7 +220,7 @@ public async Task Delete() public async Task GetCurrentFlow() { if (_flow.Id == null) - throw new InvalidOperationException("Flow has not been created yet."); + throw new FlowNotInitializedException(); return await _flowService.GetFlowById(_flow.Id); } @@ -233,9 +234,8 @@ public async Task> GetAllFlows() public async Task FromExisting(string flowId) { var existingFlow = await _flowService.GetFlowById(flowId); - if (existingFlow == null) - throw new ArgumentException("Flow not found", nameof(flowId)); - - return this; + return existingFlow == null + ? throw new FlowFoundException(flowId) + : this; } } \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/McpContext.cs b/src/MaIN.Core/Hub/Contexts/McpContext.cs index 3aa8507e..0ad7ad16 100644 --- a/src/MaIN.Core/Hub/Contexts/McpContext.cs +++ b/src/MaIN.Core/Hub/Contexts/McpContext.cs @@ -1,6 +1,7 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Services.Constants; using MaIN.Services.Services; using MaIN.Services.Services.Abstract; @@ -35,7 +36,7 @@ public async Task PromptAsync(string prompt) { if (_mcpConfig == null) { - throw new InvalidOperationException("MCP config not found"); + throw new MPCConfigNotFoundException(); } return await _mcpService.Prompt(_mcpConfig!, [new Message() diff --git a/src/MaIN.Domain/Exceptions/AIHubNotInitializedException.cs b/src/MaIN.Domain/Exceptions/AIHubNotInitializedException.cs new file mode 100644 index 00000000..118ce30d --- /dev/null +++ b/src/MaIN.Domain/Exceptions/AIHubNotInitializedException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class AIHubNotInitializedException() + : MaINCustomException("AIHub has not been initialized. Make sure to call 'AddAIHub' in your service configuration.") +{ + public override string PublicErrorMessage => LogMessage; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.Conflict; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/APIKeyNotConfiguredException.cs b/src/MaIN.Domain/Exceptions/APIKeyNotConfiguredException.cs new file mode 100644 index 00000000..dddf9a3f --- /dev/null +++ b/src/MaIN.Domain/Exceptions/APIKeyNotConfiguredException.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class APIKeyNotConfiguredException(string apiName) : MaINCustomException($"The API key of '{apiName}' has not been configured.") +{ + public override string PublicErrorMessage => "Agent not found."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.InternalServerError; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/AgentAlreadyExistsException.cs b/src/MaIN.Domain/Exceptions/AgentAlreadyExistsException.cs new file mode 100644 index 00000000..c46b3c95 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/AgentAlreadyExistsException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class AgentAlreadyExistsException(string agentId) + : MaINCustomException($"Agent with id: '{agentId}' already exists.") +{ + public override string PublicErrorMessage => "Agent already exists."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.Conflict; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/ChatAlreadyExistsException.cs b/src/MaIN.Domain/Exceptions/ChatAlreadyExistsException.cs new file mode 100644 index 00000000..8f7e56f9 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/ChatAlreadyExistsException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class ChatAlreadyExistsException(string chatId) + : MaINCustomException($"Chat with id: '{chatId}' already exists.") +{ + public override string PublicErrorMessage => "Chat already exists."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.Conflict; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/FlowAlreadyExistsException.cs b/src/MaIN.Domain/Exceptions/FlowAlreadyExistsException.cs new file mode 100644 index 00000000..72ee62ff --- /dev/null +++ b/src/MaIN.Domain/Exceptions/FlowAlreadyExistsException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class FlowAlreadyExistsException(string flowId) + : MaINCustomException($"Flow with id: '{flowId}' already exists.") +{ + public override string PublicErrorMessage => "Flow already exists."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.Conflict; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/FlowFoundException.cs b/src/MaIN.Domain/Exceptions/FlowFoundException.cs new file mode 100644 index 00000000..89582f8c --- /dev/null +++ b/src/MaIN.Domain/Exceptions/FlowFoundException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class FlowFoundException(string flowId) + : MaINCustomException($"Flow with id: '{flowId}' not found.") +{ + public override string PublicErrorMessage => "Flow not found."; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.NotFound; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/FlowNotInitializedException.cs b/src/MaIN.Domain/Exceptions/FlowNotInitializedException.cs new file mode 100644 index 00000000..4c08aa05 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/FlowNotInitializedException.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class FlowNotInitializedException() : MaINCustomException("Flow has not been created yet.") +{ + public override string PublicErrorMessage => LogMessage; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.Conflict; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/MPCConfigNotFoundException.cs b/src/MaIN.Domain/Exceptions/MPCConfigNotFoundException.cs new file mode 100644 index 00000000..2460a1e8 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/MPCConfigNotFoundException.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class MPCConfigNotFoundException() : MaINCustomException("MPC configuration not found.") +{ + public override string PublicErrorMessage => LogMessage; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.NotFound; +} \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs index ac119f64..b502248d 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using MaIN.Domain.Exceptions; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; @@ -17,7 +18,7 @@ public async Task> GetAllFlows() => public async Task AddFlow(AgentFlowDocument flow) { if (!_flows.TryAdd(flow.Id, flow)) - throw new InvalidOperationException($"Flow with ID {flow.Id} already exists."); + throw new FlowAlreadyExistsException(flow.Id); await Task.CompletedTask; } diff --git a/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs index 0993f33f..b5b8fdc1 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using MaIN.Domain.Exceptions; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; @@ -20,7 +21,7 @@ public async Task AddAgent(AgentDocument agent) throw new ArgumentNullException(nameof(agent)); if (!_agents.TryAdd(agent.Id, agent)) - throw new InvalidOperationException($"Agent with ID {agent.Id} already exists."); + throw new AgentAlreadyExistsException(agent.Id); await Task.CompletedTask; } @@ -28,7 +29,7 @@ public async Task AddAgent(AgentDocument agent) public async Task UpdateAgent(string id, AgentDocument agent) { if (!_agents.TryUpdate(id, agent, _agents.GetValueOrDefault(id)!)) - throw new KeyNotFoundException($"Agent with ID {id} not found."); + throw new AgentNotFoundException(id); await Task.CompletedTask; } @@ -36,7 +37,7 @@ public async Task UpdateAgent(string id, AgentDocument agent) public async Task DeleteAgent(string id) { if (!_agents.TryRemove(id, out _)) - throw new KeyNotFoundException($"Agent with ID {id} not found."); + throw new AgentNotFoundException(id); await Task.CompletedTask; } diff --git a/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs index c8aff5bc..8c3baa52 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using MaIN.Domain.Exceptions; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; @@ -17,7 +18,7 @@ public async Task> GetAllChats() => public async Task AddChat(ChatDocument chat) { if (!_chats.TryAdd(chat.Id, chat)) - throw new InvalidOperationException($"Chat with ID {chat.Id} already exists."); + throw new ChatAlreadyExistsException(chat.Id); await Task.CompletedTask; } diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs index 956219e4..3c4530ad 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using MaIN.Domain.Exceptions; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; @@ -45,7 +46,7 @@ public async Task AddFlow(AgentFlowDocument flow) { var filePath = GetFilePath(flow.Id); if (File.Exists(filePath)) - throw new InvalidOperationException($"Flow with ID {flow.Id} already exists."); + throw new FlowAlreadyExistsException(flow.Id); var json = JsonSerializer.Serialize(flow, JsonOptions); await File.WriteAllTextAsync(filePath, json); diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs index 8f674666..9fbecc21 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using MaIN.Domain.Exceptions; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; @@ -48,7 +49,7 @@ public async Task AddAgent(AgentDocument agent) var filePath = GetFilePath(agent.Id); if (File.Exists(filePath)) - throw new InvalidOperationException($"Agent with ID {agent.Id} already exists."); + throw new AgentAlreadyExistsException(agent.Id); var json = JsonSerializer.Serialize(agent, JsonOptions); await File.WriteAllTextAsync(filePath, json); diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs index 00dc42ad..e66fe0d1 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using MaIN.Domain.Exceptions; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; @@ -45,7 +46,7 @@ public async Task AddChat(ChatDocument chat) { var filePath = GetFilePath(chat.Id); if (File.Exists(filePath)) - throw new InvalidOperationException($"Chat with ID {chat.Id} already exists."); + throw new ChatAlreadyExistsException(chat.Id); var json = JsonSerializer.Serialize(chat, JsonOptions); await File.WriteAllTextAsync(filePath, json); diff --git a/src/MaIN.Services/Services/LLMService/AnthropicService.cs b/src/MaIN.Services/Services/LLMService/AnthropicService.cs index 56b4554f..54c75595 100644 --- a/src/MaIN.Services/Services/LLMService/AnthropicService.cs +++ b/src/MaIN.Services/Services/LLMService/AnthropicService.cs @@ -11,6 +11,7 @@ using LLama.Common; using MaIN.Domain.Configuration; using MaIN.Domain.Entities.Tools; +using MaIN.Domain.Exceptions; using MaIN.Services.Services.LLMService.Utils; using MaIN.Services.Services.LLMService; @@ -43,14 +44,14 @@ private HttpClient CreateAnthropicHttpClient() private string GetApiKey() { return _settings.AnthropicKey ?? Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? - throw new InvalidOperationException("Anthropic Key not configured"); + throw new APIKeyNotConfiguredException("Anthropic"); } private void ValidateApiKey() { if (string.IsNullOrEmpty(_settings.AnthropicKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"))) { - throw new InvalidOperationException("Anthropic Key not configured"); + throw new APIKeyNotConfiguredException("Anthropic"); } } diff --git a/src/MaIN.Services/Services/LLMService/DeepSeekService.cs b/src/MaIN.Services/Services/LLMService/DeepSeekService.cs index ec617d5d..758d27d2 100644 --- a/src/MaIN.Services/Services/LLMService/DeepSeekService.cs +++ b/src/MaIN.Services/Services/LLMService/DeepSeekService.cs @@ -9,6 +9,7 @@ using MaIN.Domain.Models; using System.Text.Json; using System.Text.Json.Serialization; +using MaIN.Domain.Exceptions; namespace MaIN.Services.Services.LLMService; @@ -33,7 +34,7 @@ public sealed class DeepSeekService( protected override string GetApiKey() { return _settings.DeepSeekKey ?? Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY") ?? - throw new InvalidOperationException("DeepSeek Key not configured"); + throw new APIKeyNotConfiguredException("DeepSeek"); } protected override void ValidateApiKey() @@ -41,7 +42,7 @@ protected override void ValidateApiKey() if (string.IsNullOrEmpty(_settings.DeepSeekKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY"))) { - throw new InvalidOperationException("DeepSeek Key not configured"); + throw new APIKeyNotConfiguredException("DeepSeek"); } } diff --git a/src/MaIN.Services/Services/LLMService/GroqCloudService.cs b/src/MaIN.Services/Services/LLMService/GroqCloudService.cs index 79dbf304..b04c530c 100644 --- a/src/MaIN.Services/Services/LLMService/GroqCloudService.cs +++ b/src/MaIN.Services/Services/LLMService/GroqCloudService.cs @@ -1,6 +1,7 @@ using System.Text; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Services.Services.Abstract; using Microsoft.Extensions.Logging; using MaIN.Services.Services.LLMService.Memory; @@ -27,14 +28,14 @@ public sealed class GroqCloudService( protected override string GetApiKey() { return _settings.GroqCloudKey ?? Environment.GetEnvironmentVariable("GROQ_API_KEY") ?? - throw new InvalidOperationException("GroqCloud Key not configured"); + throw new APIKeyNotConfiguredException("GroqCloud"); } protected override void ValidateApiKey() { if (string.IsNullOrEmpty(_settings.GroqCloudKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GROQ_API_KEY"))) { - throw new InvalidOperationException("GroqCloud Key not configured"); + throw new APIKeyNotConfiguredException("GroqCloud"); } } @@ -63,14 +64,14 @@ protected override void ValidateApiKey() private string ComposeMessage(Message lastMsg, string[] filePaths) { var stringBuilder = new StringBuilder(); - stringBuilder.AppendLine($"== FILES IN MEMORY"); + stringBuilder.AppendLine("== FILES IN MEMORY"); foreach (var path in filePaths) { var doc = DocumentProcessor.ProcessDocument(path); stringBuilder.Append(doc); stringBuilder.AppendLine(); } - stringBuilder.AppendLine($"== END OF FILES"); + stringBuilder.AppendLine("== END OF FILES"); stringBuilder.AppendLine(); stringBuilder.Append(lastMsg.Content); return stringBuilder.ToString(); diff --git a/src/MaIN.Services/Services/LLMService/OpenAiService.cs b/src/MaIN.Services/Services/LLMService/OpenAiService.cs index 8bd68b34..72991bb6 100644 --- a/src/MaIN.Services/Services/LLMService/OpenAiService.cs +++ b/src/MaIN.Services/Services/LLMService/OpenAiService.cs @@ -1,4 +1,5 @@ using MaIN.Domain.Configuration; +using MaIN.Domain.Exceptions; using MaIN.Services.Services.Abstract; using Microsoft.Extensions.Logging; using MaIN.Services.Services.LLMService.Memory; @@ -18,15 +19,15 @@ public sealed class OpenAiService( protected override string GetApiKey() { - return _settings.OpenAiKey ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? - throw new InvalidOperationException("OpenAi Key not configured"); + return _settings.OpenAiKey ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? + throw new APIKeyNotConfiguredException("OpenAi"); } protected override void ValidateApiKey() { if (string.IsNullOrEmpty(_settings.OpenAiKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))) { - throw new InvalidOperationException("OpenAi Key not configured"); + throw new APIKeyNotConfiguredException("OpenAi"); } } diff --git a/src/MaIN.Services/Services/Steps/McpStepHandler.cs b/src/MaIN.Services/Services/Steps/McpStepHandler.cs index b768f4ac..24dc4d5c 100644 --- a/src/MaIN.Services/Services/Steps/McpStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/McpStepHandler.cs @@ -17,7 +17,7 @@ public async Task Handle(StepContext context) { if (context.McpConfig == null) { - throw new MissingFieldException("MCP config is missing"); + throw new MPCConfigNotFoundException(); } await context.NotifyProgress("true", context.Agent.Id, null, context.Agent.CurrentBehaviour, StepName); From 776f846afcced59506c3f131e0132509aec16559 Mon Sep 17 00:00:00 2001 From: Szymon Gurak Date: Wed, 7 Jan 2026 22:53:14 +0100 Subject: [PATCH 6/9] [#19] Add error handling in InferPage --- .../Components/Pages/Home.razor | 93 +++++++++++++------ src/MaIN.InferPage/wwwroot/home.css | 41 ++++++++ 2 files changed, 105 insertions(+), 29 deletions(-) diff --git a/src/MaIN.InferPage/Components/Pages/Home.razor b/src/MaIN.InferPage/Components/Pages/Home.razor index f7496366..d14f0ad0 100644 --- a/src/MaIN.InferPage/Components/Pages/Home.razor +++ b/src/MaIN.InferPage/Components/Pages/Home.razor @@ -13,6 +13,23 @@ MaIN Infer +
+ @if (!string.IsNullOrEmpty(_errorMessage)) + { +
+ + Error + @_errorMessage + +
+ } +
+ @@ -183,6 +200,7 @@ private bool _isLoading; private bool _isThinking; private bool _reasoning; + private string? _errorMessage; private string? _incomingMessage = null; private string? _incomingReasoning = null; private readonly string? _displayName = Utils.Model; @@ -249,38 +267,56 @@ _prompt = string.Empty; StateHasChanged(); bool wasAtBottom = await JS.InvokeAsync("scrollManager.isAtBottom", "messages-container"); - await ctx!.WithMessage(msg) - .CompleteAsync(changeOfValue: async message => - { - if (message?.Type == TokenType.Reason) - { - _isThinking = true; - _incomingReasoning += message.Text; - } - else if (message?.Type == TokenType.Message) - { - _isThinking = false; - _incomingMessage += message.Text; - } - StateHasChanged(); - if (wasAtBottom) + try + { + await ctx!.WithMessage(msg) + .CompleteAsync(changeOfValue: async message => { - await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", _bottomElement); - } - }); + if (message?.Type == TokenType.Reason) + { + _isThinking = true; + _incomingReasoning += message.Text; + } + else if (message?.Type == TokenType.Message) + { + _isThinking = false; + _incomingMessage += message.Text; + } + + StateHasChanged(); + if (wasAtBottom) + { + await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", _bottomElement); + } + }); + + _isLoading = false; + var currentChat = (await ctx.GetCurrentChat()); + Chat.Messages.Add(currentChat.Messages.Last()); + Messages = Chat.Messages.Select(x => new MessageExt() + { + Message = x + }).ToList(); + _incomingReasoning = null; + _incomingMessage = null; + await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", _bottomElement); + StateHasChanged(); - _isLoading = false; - var currentChat = (await ctx.GetCurrentChat()); - Chat.Messages.Add(currentChat.Messages.Last()); - Messages = Chat.Messages.Select(x => new MessageExt() + } + catch (Exception ex) { - Message = x - }).ToList(); - _incomingReasoning = null; - _incomingMessage = null; - await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", _bottomElement); - StateHasChanged(); + _errorMessage = null; + StateHasChanged(); + + _errorMessage = $"{ex.Message}"; + } + finally + { + _isLoading = false; + _isThinking = false; + StateHasChanged(); + } } } @@ -288,5 +324,4 @@ { _prompt = obj.Value?.ToString()!; } - } \ No newline at end of file diff --git a/src/MaIN.InferPage/wwwroot/home.css b/src/MaIN.InferPage/wwwroot/home.css index 50421e1d..2c663a08 100644 --- a/src/MaIN.InferPage/wwwroot/home.css +++ b/src/MaIN.InferPage/wwwroot/home.css @@ -57,4 +57,45 @@ .message-card p { margin: 0; +} + +.error-notification-wrapper { + position: fixed; + top: 30px; + right: 30px; + z-index: 9999; + width: 550px; + filter: drop-shadow(0 10px 15px rgba(0, 0, 0, 0.2)); + pointer-events: none; +} + +.error-notification-wrapper > div { + pointer-events: auto; +} + +.high-alert-error { + background-color: #d32f2f !important; + color: #ffffff !important; + border: none !important; + font-size: 1.1rem !important; +} + +.high-alert-error * { + color: #ffffff !important; + fill: #ffffff !important; +} + +.high-alert-error .content { + padding: 16px !important; +} + +.error-shake { + animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both; +} + +@keyframes shake { + 10%, 90% { transform: translate3d(-1px, 0, 0); } + 20%, 80% { transform: translate3d(2px, 0, 0); } + 30%, 50%, 70% { transform: translate3d(-4px, 0, 0); } + 40%, 60% { transform: translate3d(4px, 0, 0); } } \ No newline at end of file From c6bc62c6ca6cc5debaff4828ed1cb354aaebde05 Mon Sep 17 00:00:00 2001 From: Szymon Gurak Date: Sun, 11 Jan 2026 21:46:40 +0100 Subject: [PATCH 7/9] [#19] Add handling of LLM API errors, fix namespaces --- .../Flows/AgentsComposedAsFlowExample.cs | 2 +- Examples/Examples/Chat/ChatExampleXai.cs | 2 +- .../Examples/Chat/ChatFromExistingExample.cs | 1 + Examples/Examples/IExample.cs | 2 + Examples/Examples/Mcp/McpAgentsExample.cs | 2 +- Examples/Examples/Mcp/McpExample.cs | 2 +- Examples/Examples/Program.cs | 89 ++++++------ src/MaIN.Core/Hub/Contexts/AgentContext.cs | 1 + src/MaIN.Core/Hub/Contexts/ChatContext.cs | 1 + src/MaIN.Core/Hub/Contexts/FlowContext.cs | 1 + src/MaIN.Core/Hub/Contexts/McpContext.cs | 1 + src/MaIN.Core/Hub/Contexts/ModelContext.cs | 4 +- .../Configuration/FileSystemSettings.cs | 2 +- src/MaIN.Domain/Configuration/MaINSettings.cs | 2 - .../Configuration/MongoDbSettings.cs | 2 +- .../APIKeyNotConfiguredException.cs | 2 +- .../AgentAlreadyExistsException.cs | 2 +- .../AgentContextNotFoundException.cs | 2 +- .../AgentFlowNotFoundException.cs | 2 +- .../{ => Agents}/AgentNotFoundException.cs | 2 +- .../{ => Chats}/ChatAlreadyExistsException.cs | 2 +- .../{ => Chats}/ChatNotFoundException.cs | 2 +- .../ChatNotInitializedException.cs | 2 +- .../{ => Chats}/EmptyChatException.cs | 2 +- .../{ => Flows}/FlowAlreadyExistsException.cs | 2 +- .../{ => Flows}/FlowFoundException.cs | 2 +- .../FlowNotInitializedException.cs | 2 +- src/MaIN.Domain/Exceptions/LLMApiException.cs | 10 ++ .../{ => MPC}/MPCConfigNotFoundException.cs | 2 +- .../ModelNotDownloadedException.cs | 2 +- .../ModelNotSupportedException.cs | 2 +- .../Models/ModelsPathNotFoundException.cs | 9 ++ src/MaIN.Domain/Models/SupportedModels.cs | 1 + .../Components/Pages/ErrorNotification.razor | 29 ++++ .../Components/Pages/Home.razor | 27 ++-- src/MaIN.InferPage/Program.cs | 21 +-- src/MaIN.InferPage/wwwroot/home.css | 11 +- src/MaIN.Infrastructure/Bootstrapper.cs | 1 + .../Models/AgentContextDocument.cs | 1 - .../Models/AgentSourceDocument.cs | 2 +- .../Models/AgentSourceTypeDocument.cs | 2 +- .../DefaultAgentFlowRepository.cs | 1 + .../Repositories/DefaultAgentRepository.cs | 1 + .../Repositories/DefaultChatRepository.cs | 1 + .../FileSystemAgentFlowRepository.cs | 1 + .../FileSystem/FileSystemAgentRepository.cs | 1 + .../FileSystem/FileSystemChatRepository.cs | 1 + .../Mongo/MongoAgentFlowRepository.cs | 2 +- .../Mongo/MongoAgentRepository.cs | 2 +- .../Repositories/Mongo/MongoChatRepository.cs | 2 +- .../Sqlite/SqliteChatRepository.cs | 2 + src/MaIN.Services/Bootstrapper.cs | 1 + src/MaIN.Services/Mappers/AgentMapper.cs | 1 - .../Services/AgentFlowService.cs | 1 + src/MaIN.Services/Services/AgentService.cs | 2 + src/MaIN.Services/Services/ChatService.cs | 1 + .../ImageGenServices/GeminiImageGenService.cs | 6 +- .../ImageGenServices/ImageGenDalleService.cs | 7 +- .../ImageGenServices/XaiImageGenService.cs | 6 +- .../Services/LLMService/AnthropicService.cs | 129 +++++++++++++----- .../Services/LLMService/DeepSeekService.cs | 11 +- .../Services/LLMService/GeminiService.cs | 12 +- .../Services/LLMService/GroqCloudService.cs | 12 +- .../Services/LLMService/LLMService.cs | 11 +- .../LLamaSharpTextEmbeddingMaINClone.cs | 8 +- .../LLMService/Memory/IMemoryFactory.cs | 1 + .../Memory/KernelMemoryLlamaExtensions.cs | 1 + .../LLMService/Memory/MemoryFactory.cs | 12 +- .../LLMService/Memory/MemoryService.cs | 2 + .../LLMService/OpenAiCompatibleService.cs | 58 +++++++- .../Services/LLMService/OpenAiService.cs | 11 +- .../LLMService/Utils/LLMApiRegistry.cs | 13 ++ .../Services/LLMService/XaiService.cs | 12 +- src/MaIN.Services/Services/McpService.cs | 10 +- .../Services/Models/Commands/AnswerCommand.cs | 1 + .../Services/Models/Commands/FetchCommand.cs | 1 + .../Services/Models/Commands/McpCommand.cs | 1 + .../Models/Commands/RedirectCommand.cs | 1 + .../Services/Models/Commands/StartCommand.cs | 1 + .../Services/Steps/AnswerStepHandler.cs | 1 + .../Steps/Commands/Abstract/ICommand.cs | 2 +- .../Commands/Abstract/ICommandDispatcher.cs | 2 +- .../Commands/Abstract/ICommandHandler.cs | 2 +- .../Steps/Commands/AnswerCommandHandler.cs | 1 + .../Steps/Commands/CommandDispatcher.cs | 2 + .../Steps/Commands/FetchCommandHandler.cs | 1 + .../Steps/Commands/McpCommandHandler.cs | 1 + .../Steps/Commands/RedirectCommandHandler.cs | 1 + .../Steps/Commands/StartCommandHandler.cs | 1 + .../Services/Steps/FechDataStepHandler.cs | 1 + .../Services/Steps/McpStepHandler.cs | 2 + .../Services/Steps/RedirectStepHandler.cs | 1 + .../TTSService/TextToSpeechService.cs | 11 +- src/MaIN.Services/Utils/JsonCleaner.cs | 6 +- 94 files changed, 435 insertions(+), 205 deletions(-) rename src/MaIN.Domain/Exceptions/{ => Agents}/AgentAlreadyExistsException.cs (88%) rename src/MaIN.Domain/Exceptions/{ => Agents}/AgentContextNotFoundException.cs (88%) rename src/MaIN.Domain/Exceptions/{ => Agents}/AgentFlowNotFoundException.cs (88%) rename src/MaIN.Domain/Exceptions/{ => Agents}/AgentNotFoundException.cs (87%) rename src/MaIN.Domain/Exceptions/{ => Chats}/ChatAlreadyExistsException.cs (88%) rename src/MaIN.Domain/Exceptions/{ => Chats}/ChatNotFoundException.cs (87%) rename src/MaIN.Domain/Exceptions/{ => Chats}/ChatNotInitializedException.cs (88%) rename src/MaIN.Domain/Exceptions/{ => Chats}/EmptyChatException.cs (90%) rename src/MaIN.Domain/Exceptions/{ => Flows}/FlowAlreadyExistsException.cs (88%) rename src/MaIN.Domain/Exceptions/{ => Flows}/FlowFoundException.cs (87%) rename src/MaIN.Domain/Exceptions/{ => Flows}/FlowNotInitializedException.cs (86%) create mode 100644 src/MaIN.Domain/Exceptions/LLMApiException.cs rename src/MaIN.Domain/Exceptions/{ => MPC}/MPCConfigNotFoundException.cs (87%) rename src/MaIN.Domain/Exceptions/{ => Models}/ModelNotDownloadedException.cs (88%) rename src/MaIN.Domain/Exceptions/{ => Models}/ModelNotSupportedException.cs (88%) create mode 100644 src/MaIN.Domain/Exceptions/Models/ModelsPathNotFoundException.cs create mode 100644 src/MaIN.InferPage/Components/Pages/ErrorNotification.razor create mode 100644 src/MaIN.Services/Services/LLMService/Utils/LLMApiRegistry.cs diff --git a/Examples/Examples/Agents/Flows/AgentsComposedAsFlowExample.cs b/Examples/Examples/Agents/Flows/AgentsComposedAsFlowExample.cs index b92b8bc6..5ba8f6ab 100644 --- a/Examples/Examples/Agents/Flows/AgentsComposedAsFlowExample.cs +++ b/Examples/Examples/Agents/Flows/AgentsComposedAsFlowExample.cs @@ -1,7 +1,7 @@ using MaIN.Core.Hub; using MaIN.Core.Hub.Utils; -namespace Examples.Agents; +namespace Examples.Agents.Flows; public class AgentsComposedAsFlowExample : IExample { diff --git a/Examples/Examples/Chat/ChatExampleXai.cs b/Examples/Examples/Chat/ChatExampleXai.cs index 9f0e7581..2f8df21c 100644 --- a/Examples/Examples/Chat/ChatExampleXai.cs +++ b/Examples/Examples/Chat/ChatExampleXai.cs @@ -1,7 +1,7 @@ using Examples.Utils; using MaIN.Core.Hub; -namespace Examples; +namespace Examples.Chat; public class ChatExampleXai : IExample { diff --git a/Examples/Examples/Chat/ChatFromExistingExample.cs b/Examples/Examples/Chat/ChatFromExistingExample.cs index bebcf457..416561a4 100644 --- a/Examples/Examples/Chat/ChatFromExistingExample.cs +++ b/Examples/Examples/Chat/ChatFromExistingExample.cs @@ -1,6 +1,7 @@ using System.Text.Json; using MaIN.Core.Hub; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Chats; namespace Examples.Chat; diff --git a/Examples/Examples/IExample.cs b/Examples/Examples/IExample.cs index 6144d16c..25357fa9 100644 --- a/Examples/Examples/IExample.cs +++ b/Examples/Examples/IExample.cs @@ -1,3 +1,5 @@ +namespace Examples; + public interface IExample { Task Start(); diff --git a/Examples/Examples/Mcp/McpAgentsExample.cs b/Examples/Examples/Mcp/McpAgentsExample.cs index 155d2312..fac050ba 100644 --- a/Examples/Examples/Mcp/McpAgentsExample.cs +++ b/Examples/Examples/Mcp/McpAgentsExample.cs @@ -2,7 +2,7 @@ using MaIN.Core.Hub.Utils; using MaIN.Domain.Configuration; -namespace Examples; +namespace Examples.Mcp; public class McpAgentsExample : IExample { diff --git a/Examples/Examples/Mcp/McpExample.cs b/Examples/Examples/Mcp/McpExample.cs index 75b3b6e6..e3b97a24 100644 --- a/Examples/Examples/Mcp/McpExample.cs +++ b/Examples/Examples/Mcp/McpExample.cs @@ -2,7 +2,7 @@ using MaIN.Core.Hub; using MaIN.Domain.Configuration; -namespace Examples; +namespace Examples.Mcp; public class McpExample : IExample { diff --git a/Examples/Examples/Program.cs b/Examples/Examples/Program.cs index a0061284..58946225 100644 --- a/Examples/Examples/Program.cs +++ b/Examples/Examples/Program.cs @@ -4,11 +4,9 @@ using Examples.Chat; using Examples.Mcp; using MaIN.Core; -using MaIN.Domain.Exceptions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - var Banner = @" ███╗ ███╗ █████╗ ██╗███╗ ██╗ ███████╗██╗ ██╗ █████╗ ███╗ ███╗██████╗ ██╗ ███████╗███████╗ ████╗ ████║██╔══██╗██║████╗ ██║ ██╔════╝╚██╗██╔╝██╔══██╗████╗ ████║██╔══██╗██║ ██╔════╝██╔════╝ @@ -148,50 +146,53 @@ async Task RunSelectedExample(IServiceProvider serviceProvider) } -public class ExampleRegistry(IServiceProvider serviceProvider) +namespace Examples { - public List<(string Name, IExample Instance)> GetAvailableExamples() + public class ExampleRegistry(IServiceProvider serviceProvider) { - return new List<(string, IExample)> + public List<(string Name, IExample Instance)> GetAvailableExamples() { - ("\u25a0 Basic Chat", serviceProvider.GetRequiredService()), - ("\u25a0 Chat with Files", serviceProvider.GetRequiredService()), - ("\u25a0 Chat with custom grammar", serviceProvider.GetRequiredService()), - ("\u25a0 Chat with Files from stream", serviceProvider.GetRequiredService()), - ("\u25a0 Chat with Vision", serviceProvider.GetRequiredService()), - ("\u25a0 Chat with Tools (simple)", serviceProvider.GetRequiredService()), - ("\u25a0 Chat with Image Generation", serviceProvider.GetRequiredService()), - ("\u25a0 Chat from Existing", serviceProvider.GetRequiredService()), - ("\u25a0 Chat with reasoning", serviceProvider.GetRequiredService()), - ("\u25a0 Basic Agent", serviceProvider.GetRequiredService()), - ("\u25a0 Conversation Agent", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with Redirect", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with Redirect (Multi backends)", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with Redirect Image", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with Become", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with Tools (advanced)", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with Knowledge", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with Web Knowledge", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with Mcp Knowledge", serviceProvider.GetRequiredService()), - ("\u25a0 Agent with API Data Source", serviceProvider.GetRequiredService()), - ("\u25a0 Agents Talking to Each Other", serviceProvider.GetRequiredService()), - ("\u25a0 Agents Composed as Flow", serviceProvider.GetRequiredService()), - ("\u25a0 Agents Flow Loaded", serviceProvider.GetRequiredService()), - ("\u25a0 OpenAi Chat", serviceProvider.GetRequiredService()), - ("\u25a0 OpenAi Chat with image", serviceProvider.GetRequiredService()), - ("\u25a0 OpenAi Agent with Web Data Source", serviceProvider.GetRequiredService()), - ("\u25a0 Gemini Chat", serviceProvider.GetRequiredService()), - ("\u25a0 Gemini Chat with grammar", serviceProvider.GetRequiredService()), - ("\u25a0 Gemini Chat with image", serviceProvider.GetRequiredService()), - ("\u25a0 Gemini Chat with files", serviceProvider.GetRequiredService()), - ("\u25a0 DeepSeek Chat with reasoning", serviceProvider.GetRequiredService()), - ("\u25a0 GroqCloud Chat", serviceProvider.GetRequiredService()), - ("\u25a0 Anthropic Chat", serviceProvider.GetRequiredService()), - ("\u25a0 xAI Chat", serviceProvider.GetRequiredService()), - ("\u25a0 McpClient example", serviceProvider.GetRequiredService()), - ("\u25a0 McpAgent example", serviceProvider.GetRequiredService()), - ("\u25a0 Chat with TTS example", serviceProvider.GetRequiredService()), - ("\u25a0 McpAgent example", serviceProvider.GetRequiredService()) - }; + return + [ + ("\u25a0 Basic Chat", serviceProvider.GetRequiredService()), + ("\u25a0 Chat with Files", serviceProvider.GetRequiredService()), + ("\u25a0 Chat with custom grammar", serviceProvider.GetRequiredService()), + ("\u25a0 Chat with Files from stream", serviceProvider.GetRequiredService()), + ("\u25a0 Chat with Vision", serviceProvider.GetRequiredService()), + ("\u25a0 Chat with Tools (simple)", serviceProvider.GetRequiredService()), + ("\u25a0 Chat with Image Generation", serviceProvider.GetRequiredService()), + ("\u25a0 Chat from Existing", serviceProvider.GetRequiredService()), + ("\u25a0 Chat with reasoning", serviceProvider.GetRequiredService()), + ("\u25a0 Basic Agent", serviceProvider.GetRequiredService()), + ("\u25a0 Conversation Agent", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with Redirect", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with Redirect (Multi backends)", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with Redirect Image", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with Become", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with Tools (advanced)", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with Knowledge", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with Web Knowledge", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with Mcp Knowledge", serviceProvider.GetRequiredService()), + ("\u25a0 Agent with API Data Source", serviceProvider.GetRequiredService()), + ("\u25a0 Agents Talking to Each Other", serviceProvider.GetRequiredService()), + ("\u25a0 Agents Composed as Flow", serviceProvider.GetRequiredService()), + ("\u25a0 Agents Flow Loaded", serviceProvider.GetRequiredService()), + ("\u25a0 OpenAi Chat", serviceProvider.GetRequiredService()), + ("\u25a0 OpenAi Chat with image", serviceProvider.GetRequiredService()), + ("\u25a0 OpenAi Agent with Web Data Source", serviceProvider.GetRequiredService()), + ("\u25a0 Gemini Chat", serviceProvider.GetRequiredService()), + ("\u25a0 Gemini Chat with grammar", serviceProvider.GetRequiredService()), + ("\u25a0 Gemini Chat with image", serviceProvider.GetRequiredService()), + ("\u25a0 Gemini Chat with files", serviceProvider.GetRequiredService()), + ("\u25a0 DeepSeek Chat with reasoning", serviceProvider.GetRequiredService()), + ("\u25a0 GroqCloud Chat", serviceProvider.GetRequiredService()), + ("\u25a0 Anthropic Chat", serviceProvider.GetRequiredService()), + ("\u25a0 xAI Chat", serviceProvider.GetRequiredService()), + ("\u25a0 McpClient example", serviceProvider.GetRequiredService()), + ("\u25a0 McpAgent example", serviceProvider.GetRequiredService()), + ("\u25a0 Chat with TTS example", serviceProvider.GetRequiredService()), + ("\u25a0 McpAgent example", serviceProvider.GetRequiredService()) + ]; + } } } \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/AgentContext.cs b/src/MaIN.Core/Hub/Contexts/AgentContext.cs index 519e63ac..8c7f3f5b 100644 --- a/src/MaIN.Core/Hub/Contexts/AgentContext.cs +++ b/src/MaIN.Core/Hub/Contexts/AgentContext.cs @@ -9,6 +9,7 @@ using MaIN.Domain.Entities.Agents.Knowledge; using MaIN.Domain.Entities.Tools; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Agents; using MaIN.Services.Constants; namespace MaIN.Core.Hub.Contexts; diff --git a/src/MaIN.Core/Hub/Contexts/ChatContext.cs b/src/MaIN.Core/Hub/Contexts/ChatContext.cs index 1c13734b..b1f877d2 100644 --- a/src/MaIN.Core/Hub/Contexts/ChatContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ChatContext.cs @@ -2,6 +2,7 @@ using MaIN.Domain.Entities; using MaIN.Domain.Entities.Tools; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Chats; using MaIN.Domain.Models; using MaIN.Services; using MaIN.Services.Constants; diff --git a/src/MaIN.Core/Hub/Contexts/FlowContext.cs b/src/MaIN.Core/Hub/Contexts/FlowContext.cs index e9baf71b..6702f5bd 100644 --- a/src/MaIN.Core/Hub/Contexts/FlowContext.cs +++ b/src/MaIN.Core/Hub/Contexts/FlowContext.cs @@ -5,6 +5,7 @@ using MaIN.Domain.Entities.Agents; using MaIN.Domain.Entities.Agents.AgentSource; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Flows; using MaIN.Services.Dtos; using MaIN.Services.Mappers; using MaIN.Services.Services.Abstract; diff --git a/src/MaIN.Core/Hub/Contexts/McpContext.cs b/src/MaIN.Core/Hub/Contexts/McpContext.cs index 0ad7ad16..5b2f598a 100644 --- a/src/MaIN.Core/Hub/Contexts/McpContext.cs +++ b/src/MaIN.Core/Hub/Contexts/McpContext.cs @@ -2,6 +2,7 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.MPC; using MaIN.Services.Constants; using MaIN.Services.Services; using MaIN.Services.Services.Abstract; diff --git a/src/MaIN.Core/Hub/Contexts/ModelContext.cs b/src/MaIN.Core/Hub/Contexts/ModelContext.cs index f2651477..65423550 100644 --- a/src/MaIN.Core/Hub/Contexts/ModelContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ModelContext.cs @@ -1,6 +1,8 @@ using System.Diagnostics; using System.Net; using MaIN.Domain.Configuration; +using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; using MaIN.Services.Constants; using MaIN.Services.Services.LLMService.Utils; @@ -319,5 +321,5 @@ private static string FormatBytes(long bytes) private string ResolvePath(string? settingsModelsPath) => settingsModelsPath ?? Environment.GetEnvironmentVariable("MaIN_ModelsPath") - ?? throw new InvalidOperationException("Models path not found in settings or environment variables"); + ?? throw new ModelsPathNotFoundException(); } \ No newline at end of file diff --git a/src/MaIN.Domain/Configuration/FileSystemSettings.cs b/src/MaIN.Domain/Configuration/FileSystemSettings.cs index 1058adad..caa6a8dc 100644 --- a/src/MaIN.Domain/Configuration/FileSystemSettings.cs +++ b/src/MaIN.Domain/Configuration/FileSystemSettings.cs @@ -1,4 +1,4 @@ -namespace MaIN.Services.Configuration; +namespace MaIN.Domain.Configuration; public class FileSystemSettings { diff --git a/src/MaIN.Domain/Configuration/MaINSettings.cs b/src/MaIN.Domain/Configuration/MaINSettings.cs index 17f7e062..feb2961a 100644 --- a/src/MaIN.Domain/Configuration/MaINSettings.cs +++ b/src/MaIN.Domain/Configuration/MaINSettings.cs @@ -1,6 +1,4 @@ -using MaIN.Services.Configuration; - namespace MaIN.Domain.Configuration; public class MaINSettings diff --git a/src/MaIN.Domain/Configuration/MongoDbSettings.cs b/src/MaIN.Domain/Configuration/MongoDbSettings.cs index 05710380..624f2be5 100644 --- a/src/MaIN.Domain/Configuration/MongoDbSettings.cs +++ b/src/MaIN.Domain/Configuration/MongoDbSettings.cs @@ -1,4 +1,4 @@ -namespace MaIN.Services.Configuration; +namespace MaIN.Domain.Configuration; public class MongoDbSettings { diff --git a/src/MaIN.Domain/Exceptions/APIKeyNotConfiguredException.cs b/src/MaIN.Domain/Exceptions/APIKeyNotConfiguredException.cs index dddf9a3f..dcef4c48 100644 --- a/src/MaIN.Domain/Exceptions/APIKeyNotConfiguredException.cs +++ b/src/MaIN.Domain/Exceptions/APIKeyNotConfiguredException.cs @@ -4,6 +4,6 @@ namespace MaIN.Domain.Exceptions; public class APIKeyNotConfiguredException(string apiName) : MaINCustomException($"The API key of '{apiName}' has not been configured.") { - public override string PublicErrorMessage => "Agent not found."; + public override string PublicErrorMessage => Message; public override HttpStatusCode HttpStatusCode => HttpStatusCode.InternalServerError; } \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/AgentAlreadyExistsException.cs b/src/MaIN.Domain/Exceptions/Agents/AgentAlreadyExistsException.cs similarity index 88% rename from src/MaIN.Domain/Exceptions/AgentAlreadyExistsException.cs rename to src/MaIN.Domain/Exceptions/Agents/AgentAlreadyExistsException.cs index c46b3c95..25781f0e 100644 --- a/src/MaIN.Domain/Exceptions/AgentAlreadyExistsException.cs +++ b/src/MaIN.Domain/Exceptions/Agents/AgentAlreadyExistsException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Agents; public class AgentAlreadyExistsException(string agentId) : MaINCustomException($"Agent with id: '{agentId}' already exists.") diff --git a/src/MaIN.Domain/Exceptions/AgentContextNotFoundException.cs b/src/MaIN.Domain/Exceptions/Agents/AgentContextNotFoundException.cs similarity index 88% rename from src/MaIN.Domain/Exceptions/AgentContextNotFoundException.cs rename to src/MaIN.Domain/Exceptions/Agents/AgentContextNotFoundException.cs index 0126e2e1..d852cba2 100644 --- a/src/MaIN.Domain/Exceptions/AgentContextNotFoundException.cs +++ b/src/MaIN.Domain/Exceptions/Agents/AgentContextNotFoundException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Agents; public class AgentContextNotFoundException(string agentId) : MaINCustomException($"Context of agent with id: '{agentId}' not found.") { diff --git a/src/MaIN.Domain/Exceptions/AgentFlowNotFoundException.cs b/src/MaIN.Domain/Exceptions/Agents/AgentFlowNotFoundException.cs similarity index 88% rename from src/MaIN.Domain/Exceptions/AgentFlowNotFoundException.cs rename to src/MaIN.Domain/Exceptions/Agents/AgentFlowNotFoundException.cs index bb271815..dd6f9fe8 100644 --- a/src/MaIN.Domain/Exceptions/AgentFlowNotFoundException.cs +++ b/src/MaIN.Domain/Exceptions/Agents/AgentFlowNotFoundException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Agents; public class AgentFlowNotFoundException(string flowId) : MaINCustomException($"Agent flow with id: '{flowId}' not found.") { diff --git a/src/MaIN.Domain/Exceptions/AgentNotFoundException.cs b/src/MaIN.Domain/Exceptions/Agents/AgentNotFoundException.cs similarity index 87% rename from src/MaIN.Domain/Exceptions/AgentNotFoundException.cs rename to src/MaIN.Domain/Exceptions/Agents/AgentNotFoundException.cs index 7a2556dc..926d6869 100644 --- a/src/MaIN.Domain/Exceptions/AgentNotFoundException.cs +++ b/src/MaIN.Domain/Exceptions/Agents/AgentNotFoundException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Agents; public class AgentNotFoundException(string agentId) : MaINCustomException($"Agent with id: '{agentId}' not found.") diff --git a/src/MaIN.Domain/Exceptions/ChatAlreadyExistsException.cs b/src/MaIN.Domain/Exceptions/Chats/ChatAlreadyExistsException.cs similarity index 88% rename from src/MaIN.Domain/Exceptions/ChatAlreadyExistsException.cs rename to src/MaIN.Domain/Exceptions/Chats/ChatAlreadyExistsException.cs index 8f7e56f9..7f9e0044 100644 --- a/src/MaIN.Domain/Exceptions/ChatAlreadyExistsException.cs +++ b/src/MaIN.Domain/Exceptions/Chats/ChatAlreadyExistsException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Chats; public class ChatAlreadyExistsException(string chatId) : MaINCustomException($"Chat with id: '{chatId}' already exists.") diff --git a/src/MaIN.Domain/Exceptions/ChatNotFoundException.cs b/src/MaIN.Domain/Exceptions/Chats/ChatNotFoundException.cs similarity index 87% rename from src/MaIN.Domain/Exceptions/ChatNotFoundException.cs rename to src/MaIN.Domain/Exceptions/Chats/ChatNotFoundException.cs index e01d3018..0d10f489 100644 --- a/src/MaIN.Domain/Exceptions/ChatNotFoundException.cs +++ b/src/MaIN.Domain/Exceptions/Chats/ChatNotFoundException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Chats; public class ChatNotFoundException(string chatId) : MaINCustomException($"Chat with id: '{chatId}' not found.") diff --git a/src/MaIN.Domain/Exceptions/ChatNotInitializedException.cs b/src/MaIN.Domain/Exceptions/Chats/ChatNotInitializedException.cs similarity index 88% rename from src/MaIN.Domain/Exceptions/ChatNotInitializedException.cs rename to src/MaIN.Domain/Exceptions/Chats/ChatNotInitializedException.cs index 4750eb90..47f54292 100644 --- a/src/MaIN.Domain/Exceptions/ChatNotInitializedException.cs +++ b/src/MaIN.Domain/Exceptions/Chats/ChatNotInitializedException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Chats; public class ChatNotInitializedException() : MaINCustomException("Chat has not been created yet. Call 'CompleteAsync' operation first.") { diff --git a/src/MaIN.Domain/Exceptions/EmptyChatException.cs b/src/MaIN.Domain/Exceptions/Chats/EmptyChatException.cs similarity index 90% rename from src/MaIN.Domain/Exceptions/EmptyChatException.cs rename to src/MaIN.Domain/Exceptions/Chats/EmptyChatException.cs index 5157132b..147d62c6 100644 --- a/src/MaIN.Domain/Exceptions/EmptyChatException.cs +++ b/src/MaIN.Domain/Exceptions/Chats/EmptyChatException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Chats; public class EmptyChatException(string chatId) : MaINCustomException($"Chat with id: '{chatId}' is empty. Complete operation is impossible.") { diff --git a/src/MaIN.Domain/Exceptions/FlowAlreadyExistsException.cs b/src/MaIN.Domain/Exceptions/Flows/FlowAlreadyExistsException.cs similarity index 88% rename from src/MaIN.Domain/Exceptions/FlowAlreadyExistsException.cs rename to src/MaIN.Domain/Exceptions/Flows/FlowAlreadyExistsException.cs index 72ee62ff..530715bb 100644 --- a/src/MaIN.Domain/Exceptions/FlowAlreadyExistsException.cs +++ b/src/MaIN.Domain/Exceptions/Flows/FlowAlreadyExistsException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Flows; public class FlowAlreadyExistsException(string flowId) : MaINCustomException($"Flow with id: '{flowId}' already exists.") diff --git a/src/MaIN.Domain/Exceptions/FlowFoundException.cs b/src/MaIN.Domain/Exceptions/Flows/FlowFoundException.cs similarity index 87% rename from src/MaIN.Domain/Exceptions/FlowFoundException.cs rename to src/MaIN.Domain/Exceptions/Flows/FlowFoundException.cs index 89582f8c..cc0d2492 100644 --- a/src/MaIN.Domain/Exceptions/FlowFoundException.cs +++ b/src/MaIN.Domain/Exceptions/Flows/FlowFoundException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Flows; public class FlowFoundException(string flowId) : MaINCustomException($"Flow with id: '{flowId}' not found.") diff --git a/src/MaIN.Domain/Exceptions/FlowNotInitializedException.cs b/src/MaIN.Domain/Exceptions/Flows/FlowNotInitializedException.cs similarity index 86% rename from src/MaIN.Domain/Exceptions/FlowNotInitializedException.cs rename to src/MaIN.Domain/Exceptions/Flows/FlowNotInitializedException.cs index 4c08aa05..801b4949 100644 --- a/src/MaIN.Domain/Exceptions/FlowNotInitializedException.cs +++ b/src/MaIN.Domain/Exceptions/Flows/FlowNotInitializedException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Flows; public class FlowNotInitializedException() : MaINCustomException("Flow has not been created yet.") { diff --git a/src/MaIN.Domain/Exceptions/LLMApiException.cs b/src/MaIN.Domain/Exceptions/LLMApiException.cs new file mode 100644 index 00000000..bc5a35c1 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/LLMApiException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions; + +public class LLMApiException(string llmApiName, HttpStatusCode llmApiHttpStatusCode, string? errorMessage) + : MaINCustomException($"{llmApiName} error. {errorMessage ?? string.Empty}") +{ + public override string PublicErrorMessage => Message; + public override HttpStatusCode HttpStatusCode => llmApiHttpStatusCode; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Exceptions/MPCConfigNotFoundException.cs b/src/MaIN.Domain/Exceptions/MPC/MPCConfigNotFoundException.cs similarity index 87% rename from src/MaIN.Domain/Exceptions/MPCConfigNotFoundException.cs rename to src/MaIN.Domain/Exceptions/MPC/MPCConfigNotFoundException.cs index 2460a1e8..b89e8e60 100644 --- a/src/MaIN.Domain/Exceptions/MPCConfigNotFoundException.cs +++ b/src/MaIN.Domain/Exceptions/MPC/MPCConfigNotFoundException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.MPC; public class MPCConfigNotFoundException() : MaINCustomException("MPC configuration not found.") { diff --git a/src/MaIN.Domain/Exceptions/ModelNotDownloadedException.cs b/src/MaIN.Domain/Exceptions/Models/ModelNotDownloadedException.cs similarity index 88% rename from src/MaIN.Domain/Exceptions/ModelNotDownloadedException.cs rename to src/MaIN.Domain/Exceptions/Models/ModelNotDownloadedException.cs index 84411e3f..ebb7e1b0 100644 --- a/src/MaIN.Domain/Exceptions/ModelNotDownloadedException.cs +++ b/src/MaIN.Domain/Exceptions/Models/ModelNotDownloadedException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Models; public class ModelNotDownloadedException(string? modelName) : MaINCustomException($"Given model {modelName ?? string.Empty} is not downloaded.") diff --git a/src/MaIN.Domain/Exceptions/ModelNotSupportedException.cs b/src/MaIN.Domain/Exceptions/Models/ModelNotSupportedException.cs similarity index 88% rename from src/MaIN.Domain/Exceptions/ModelNotSupportedException.cs rename to src/MaIN.Domain/Exceptions/Models/ModelNotSupportedException.cs index 4ac460e8..a97c71c7 100644 --- a/src/MaIN.Domain/Exceptions/ModelNotSupportedException.cs +++ b/src/MaIN.Domain/Exceptions/Models/ModelNotSupportedException.cs @@ -1,6 +1,6 @@ using System.Net; -namespace MaIN.Domain.Exceptions; +namespace MaIN.Domain.Exceptions.Models; public class ModelNotSupportedException(string? modelName) : MaINCustomException($"Given model {modelName ?? string.Empty} is not supported.") diff --git a/src/MaIN.Domain/Exceptions/Models/ModelsPathNotFoundException.cs b/src/MaIN.Domain/Exceptions/Models/ModelsPathNotFoundException.cs new file mode 100644 index 00000000..c6ae6503 --- /dev/null +++ b/src/MaIN.Domain/Exceptions/Models/ModelsPathNotFoundException.cs @@ -0,0 +1,9 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions.Models; + +public class ModelsPathNotFoundException() : MaINCustomException($"Models path not found in configuration or environment variables.") +{ + public override string PublicErrorMessage => Message; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.NotFound; +} \ No newline at end of file diff --git a/src/MaIN.Domain/Models/SupportedModels.cs b/src/MaIN.Domain/Models/SupportedModels.cs index 00fd2220..2ffeb7a3 100644 --- a/src/MaIN.Domain/Models/SupportedModels.cs +++ b/src/MaIN.Domain/Models/SupportedModels.cs @@ -1,4 +1,5 @@ using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Models; namespace MaIN.Domain.Models; diff --git a/src/MaIN.InferPage/Components/Pages/ErrorNotification.razor b/src/MaIN.InferPage/Components/Pages/ErrorNotification.razor new file mode 100644 index 00000000..ddf0f5d5 --- /dev/null +++ b/src/MaIN.InferPage/Components/Pages/ErrorNotification.razor @@ -0,0 +1,29 @@ +
+ @if (!string.IsNullOrEmpty(ErrorMessage)) + { +
+ + @ErrorMessage + +
+ } +
+ +@code { + [Parameter] + public string? ErrorMessage { get; set; } + + [Parameter] + public EventCallback ErrorMessageChanged { get; set; } + + private async Task HandleDismiss() + { + ErrorMessage = null; + await ErrorMessageChanged.InvokeAsync(null); + } +} \ No newline at end of file diff --git a/src/MaIN.InferPage/Components/Pages/Home.razor b/src/MaIN.InferPage/Components/Pages/Home.razor index d14f0ad0..8d1bd4ab 100644 --- a/src/MaIN.InferPage/Components/Pages/Home.razor +++ b/src/MaIN.InferPage/Components/Pages/Home.razor @@ -4,6 +4,7 @@ @using MaIN.Core.Hub @using MaIN.Core.Hub.Contexts @using MaIN.Domain.Entities +@using MaIN.Domain.Exceptions @using MaIN.Domain.Models @using Markdig @using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular @@ -13,22 +14,7 @@ MaIN Infer -
- @if (!string.IsNullOrEmpty(_errorMessage)) - { -
- - Error - @_errorMessage - -
- } -
+ @@ -308,14 +294,17 @@ { _errorMessage = null; StateHasChanged(); - - _errorMessage = $"{ex.Message}"; + + _errorMessage = ex is MaINCustomException maInException + ? $"{maInException.PublicErrorMessage}" + : $"{ex.Message}"; + + StateHasChanged(); } finally { _isLoading = false; _isThinking = false; - StateHasChanged(); } } } diff --git a/src/MaIN.InferPage/Program.cs b/src/MaIN.InferPage/Program.cs index 292101cd..01ad7079 100644 --- a/src/MaIN.InferPage/Program.cs +++ b/src/MaIN.InferPage/Program.cs @@ -3,6 +3,7 @@ using MaIN.Domain.Models; using Microsoft.FluentUI.AspNetCore.Components; using MaIN.InferPage.Components; +using MaIN.Services.Services.LLMService.Utils; using Utils = MaIN.InferPage.Utils; var builder = WebApplication.CreateBuilder(args); @@ -49,32 +50,32 @@ { case "openai": Utils.OpenAi = true; - apiKeyVariable = "OPENAI_API_KEY"; - apiName = "OpenAI"; + apiKeyVariable = LLMApiRegistry.OpenAi.ApiKeyEnvName; + apiName = LLMApiRegistry.OpenAi.ApiName; break; case "gemini": Utils.Gemini = true; - apiKeyVariable = "GEMINI_API_KEY"; - apiName = "Gemini"; + apiKeyVariable = LLMApiRegistry.Gemini.ApiKeyEnvName; + apiName = LLMApiRegistry.Gemini.ApiName; break; case "deepseek": Utils.DeepSeek = true; - apiKeyVariable = "DEEPSEEK_API_KEY"; - apiName = "Deepseek"; + apiKeyVariable = LLMApiRegistry.Deepseek.ApiKeyEnvName; + apiName = LLMApiRegistry.Deepseek.ApiName; break; case "groqcloud": Utils.GroqCloud = true; - apiKeyVariable = "GROQ_API_KEY"; - apiName = "GroqCloud"; + apiKeyVariable = LLMApiRegistry.Groq.ApiKeyEnvName; + apiName = LLMApiRegistry.Groq.ApiName; break; case "anthropic": Utils.Anthropic = true; - apiKeyVariable = "ANTHROPIC_API_KEY"; - apiName = "Anthropic"; + apiKeyVariable = LLMApiRegistry.Anthropic.ApiKeyEnvName; + apiName = LLMApiRegistry.Anthropic.ApiName; break; } diff --git a/src/MaIN.InferPage/wwwroot/home.css b/src/MaIN.InferPage/wwwroot/home.css index 2c663a08..f1101655 100644 --- a/src/MaIN.InferPage/wwwroot/home.css +++ b/src/MaIN.InferPage/wwwroot/home.css @@ -64,7 +64,9 @@ top: 30px; right: 30px; z-index: 9999; - width: 550px; + width: auto; + min-width: 300px; + max-width: 600px; filter: drop-shadow(0 10px 15px rgba(0, 0, 0, 0.2)); pointer-events: none; } @@ -78,15 +80,22 @@ color: #ffffff !important; border: none !important; font-size: 1.1rem !important; + + overflow-wrap: break-word !important; + word-wrap: break-word !important; + word-break: break-word !important; + hyphens: auto; } .high-alert-error * { color: #ffffff !important; fill: #ffffff !important; + overflow-wrap: break-word !important; } .high-alert-error .content { padding: 16px !important; + display: block !important; } .error-shake { diff --git a/src/MaIN.Infrastructure/Bootstrapper.cs b/src/MaIN.Infrastructure/Bootstrapper.cs index 6c9b4e93..08a12600 100644 --- a/src/MaIN.Infrastructure/Bootstrapper.cs +++ b/src/MaIN.Infrastructure/Bootstrapper.cs @@ -3,6 +3,7 @@ using MaIN.Infrastructure.Repositories; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Infrastructure.Repositories.FileSystem; +using MaIN.Infrastructure.Repositories.Mongo; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; diff --git a/src/MaIN.Infrastructure/Models/AgentContextDocument.cs b/src/MaIN.Infrastructure/Models/AgentContextDocument.cs index 0da9da33..b603c908 100644 --- a/src/MaIN.Infrastructure/Models/AgentContextDocument.cs +++ b/src/MaIN.Infrastructure/Models/AgentContextDocument.cs @@ -1,5 +1,4 @@ using MaIN.Domain.Entities; -using MaIN.Models.Rag; namespace MaIN.Infrastructure.Models; diff --git a/src/MaIN.Infrastructure/Models/AgentSourceDocument.cs b/src/MaIN.Infrastructure/Models/AgentSourceDocument.cs index 436185fe..50c38437 100644 --- a/src/MaIN.Infrastructure/Models/AgentSourceDocument.cs +++ b/src/MaIN.Infrastructure/Models/AgentSourceDocument.cs @@ -1,4 +1,4 @@ -namespace MaIN.Models.Rag; +namespace MaIN.Infrastructure.Models; public class AgentSourceDocument { diff --git a/src/MaIN.Infrastructure/Models/AgentSourceTypeDocument.cs b/src/MaIN.Infrastructure/Models/AgentSourceTypeDocument.cs index 9c8ef772..48999325 100644 --- a/src/MaIN.Infrastructure/Models/AgentSourceTypeDocument.cs +++ b/src/MaIN.Infrastructure/Models/AgentSourceTypeDocument.cs @@ -1,4 +1,4 @@ -namespace MaIN.Models.Rag; +namespace MaIN.Infrastructure.Models; public enum AgentSourceTypeDocument { diff --git a/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs index b502248d..38066c4d 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Flows; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs index b5b8fdc1..d66ce6e1 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Agents; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs index 8c3baa52..6f084afb 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Chats; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs index 3c4530ad..a6e2696b 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs @@ -1,5 +1,6 @@ using System.Text.Json; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Flows; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs index 9fbecc21..3ba8e416 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs @@ -1,5 +1,6 @@ using System.Text.Json; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Agents; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs index e66fe0d1..12c3e957 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs @@ -1,5 +1,6 @@ using System.Text.Json; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Chats; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentFlowRepository.cs index ec9e1772..04b91cc9 100644 --- a/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentFlowRepository.cs @@ -2,7 +2,7 @@ using MaIN.Infrastructure.Repositories.Abstract; using MongoDB.Driver; -namespace MaIN.Infrastructure.Repositories; +namespace MaIN.Infrastructure.Repositories.Mongo; public class MongoAgentFlowRepository(IMongoDatabase database, string collectionName) : IAgentFlowRepository { diff --git a/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentRepository.cs index 11435a88..5134adfe 100644 --- a/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentRepository.cs @@ -2,7 +2,7 @@ using MaIN.Infrastructure.Repositories.Abstract; using MongoDB.Driver; -namespace MaIN.Infrastructure.Repositories; +namespace MaIN.Infrastructure.Repositories.Mongo; public class MongoAgentRepository(IMongoDatabase database, string collectionName) : IAgentRepository { diff --git a/src/MaIN.Infrastructure/Repositories/Mongo/MongoChatRepository.cs b/src/MaIN.Infrastructure/Repositories/Mongo/MongoChatRepository.cs index f844fd3b..a395199a 100644 --- a/src/MaIN.Infrastructure/Repositories/Mongo/MongoChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Mongo/MongoChatRepository.cs @@ -2,7 +2,7 @@ using MaIN.Infrastructure.Repositories.Abstract; using MongoDB.Driver; -namespace MaIN.Infrastructure.Repositories; +namespace MaIN.Infrastructure.Repositories.Mongo; public class MongoChatRepository(IMongoDatabase database, string collectionName) : IChatRepository { diff --git a/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteChatRepository.cs b/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteChatRepository.cs index cb74b115..0fbdfdfa 100644 --- a/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteChatRepository.cs @@ -5,6 +5,8 @@ using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; +namespace MaIN.Infrastructure.Repositories.Sqlite; + public class SqliteChatRepository(IDbConnection connection) : IChatRepository { private readonly JsonSerializerOptions? _jsonOptions = new() diff --git a/src/MaIN.Services/Bootstrapper.cs b/src/MaIN.Services/Bootstrapper.cs index 52ab940e..6f6ea373 100644 --- a/src/MaIN.Services/Bootstrapper.cs +++ b/src/MaIN.Services/Bootstrapper.cs @@ -11,6 +11,7 @@ using MaIN.Services.Services.Models.Commands; using MaIN.Services.Services.Steps; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; using MaIN.Services.Services.TTSService; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/src/MaIN.Services/Mappers/AgentMapper.cs b/src/MaIN.Services/Mappers/AgentMapper.cs index 1365cebf..87503f8c 100644 --- a/src/MaIN.Services/Mappers/AgentMapper.cs +++ b/src/MaIN.Services/Mappers/AgentMapper.cs @@ -2,7 +2,6 @@ using MaIN.Domain.Entities.Agents; using MaIN.Domain.Entities.Agents.AgentSource; using MaIN.Infrastructure.Models; -using MaIN.Models.Rag; using MaIN.Services.Dtos.Rag; using MaIN.Services.Dtos.Rag.AgentSource; diff --git a/src/MaIN.Services/Services/AgentFlowService.cs b/src/MaIN.Services/Services/AgentFlowService.cs index 1ab7e8ce..cab06b76 100644 --- a/src/MaIN.Services/Services/AgentFlowService.cs +++ b/src/MaIN.Services/Services/AgentFlowService.cs @@ -1,5 +1,6 @@ using MaIN.Domain.Entities.Agents.AgentSource; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Agents; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Mappers; diff --git a/src/MaIN.Services/Services/AgentService.cs b/src/MaIN.Services/Services/AgentService.cs index 07a29a5a..e20255ca 100644 --- a/src/MaIN.Services/Services/AgentService.cs +++ b/src/MaIN.Services/Services/AgentService.cs @@ -6,6 +6,7 @@ using MaIN.Domain.Entities.Tools; using MaIN.Domain.Models; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Agents; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Constants; using MaIN.Services.Mappers; @@ -14,6 +15,7 @@ using MaIN.Services.Services.LLMService.Factory; using MaIN.Services.Services.Models.Commands; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; using MaIN.Services.Utils; using Microsoft.Extensions.Logging; using static System.Text.RegularExpressions.Regex; diff --git a/src/MaIN.Services/Services/ChatService.cs b/src/MaIN.Services/Services/ChatService.cs index 3be9d874..e1a1a21b 100644 --- a/src/MaIN.Services/Services/ChatService.cs +++ b/src/MaIN.Services/Services/ChatService.cs @@ -1,6 +1,7 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Chats; using MaIN.Domain.Models; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Mappers; diff --git a/src/MaIN.Services/Services/ImageGenServices/GeminiImageGenService.cs b/src/MaIN.Services/Services/ImageGenServices/GeminiImageGenService.cs index 2926238f..8faffa08 100644 --- a/src/MaIN.Services/Services/ImageGenServices/GeminiImageGenService.cs +++ b/src/MaIN.Services/Services/ImageGenServices/GeminiImageGenService.cs @@ -6,6 +6,8 @@ using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json.Serialization; +using MaIN.Domain.Exceptions; +using MaIN.Services.Services.LLMService.Utils; namespace MaIN.Services.Services.ImageGenServices; @@ -17,8 +19,8 @@ internal class GeminiImageGenService(IHttpClientFactory httpClientFactory, MaINS public async Task Send(Chat chat) { var client = _httpClientFactory.CreateClient(ServiceConstants.HttpClients.GeminiClient); - string apiKey = _settings.GeminiKey ?? Environment.GetEnvironmentVariable("GEMINI_API_KEY") - ?? throw new InvalidOperationException("Gemini API key is not configured"); + string apiKey = _settings.GeminiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Gemini.ApiKeyEnvName) + ?? throw new APIKeyNotConfiguredException(LLMApiRegistry.Gemini.ApiName); if (string.IsNullOrEmpty(chat.Model)) { diff --git a/src/MaIN.Services/Services/ImageGenServices/ImageGenDalleService.cs b/src/MaIN.Services/Services/ImageGenServices/ImageGenDalleService.cs index eb1c91f5..7a0e2543 100644 --- a/src/MaIN.Services/Services/ImageGenServices/ImageGenDalleService.cs +++ b/src/MaIN.Services/Services/ImageGenServices/ImageGenDalleService.cs @@ -2,8 +2,10 @@ using System.Net.Http.Json; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Services.Constants; using MaIN.Services.Services.Abstract; +using MaIN.Services.Services.LLMService.Utils; using MaIN.Services.Services.Models; namespace MaIN.Services.Services.ImageGenServices; @@ -19,8 +21,8 @@ public class OpenAiImageGenService( public async Task Send(Chat chat) { var client = _httpClientFactory.CreateClient(ServiceConstants.HttpClients.OpenAiClient); - string apiKey = _settings.OpenAiKey ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY") - ?? throw new InvalidOperationException("OpenAI API key is not configured"); + string apiKey = _settings.OpenAiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.OpenAi.ApiKeyEnvName) + ?? throw new APIKeyNotConfiguredException(LLMApiRegistry.OpenAi.ApiName); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); var requestBody = new @@ -84,7 +86,6 @@ private struct Models } } - file class OpenAiImageResponse { public ImageData[] Data { get; set; } = []; diff --git a/src/MaIN.Services/Services/ImageGenServices/XaiImageGenService.cs b/src/MaIN.Services/Services/ImageGenServices/XaiImageGenService.cs index e6be9f6c..ac3ce8c7 100644 --- a/src/MaIN.Services/Services/ImageGenServices/XaiImageGenService.cs +++ b/src/MaIN.Services/Services/ImageGenServices/XaiImageGenService.cs @@ -6,6 +6,8 @@ using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; +using MaIN.Domain.Exceptions; +using MaIN.Services.Services.LLMService.Utils; namespace MaIN.Services.Services.ImageGenServices; @@ -20,8 +22,8 @@ public class XaiImageGenService( public async Task Send(Chat chat) { var client = _httpClientFactory.CreateClient(ServiceConstants.HttpClients.XaiClient); - string apiKey = _settings.XaiKey ?? Environment.GetEnvironmentVariable("XAI_API_KEY") ?? - throw new InvalidOperationException("xAI Key not configured"); + string apiKey = _settings.XaiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Xai.ApiKeyEnvName) ?? + throw new APIKeyNotConfiguredException(LLMApiRegistry.Xai.ApiName); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); var requestBody = new diff --git a/src/MaIN.Services/Services/LLMService/AnthropicService.cs b/src/MaIN.Services/Services/LLMService/AnthropicService.cs index 54c75595..d7d54c6d 100644 --- a/src/MaIN.Services/Services/LLMService/AnthropicService.cs +++ b/src/MaIN.Services/Services/LLMService/AnthropicService.cs @@ -43,15 +43,16 @@ private HttpClient CreateAnthropicHttpClient() private string GetApiKey() { - return _settings.AnthropicKey ?? Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? - throw new APIKeyNotConfiguredException("Anthropic"); + return _settings.AnthropicKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Anthropic.ApiKeyEnvName) ?? + throw new APIKeyNotConfiguredException(LLMApiRegistry.Anthropic.ApiName); } private void ValidateApiKey() { - if (string.IsNullOrEmpty(_settings.AnthropicKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"))) + if (string.IsNullOrEmpty(_settings.AnthropicKey) && + string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LLMApiRegistry.Anthropic.ApiKeyEnvName))) { - throw new APIKeyNotConfiguredException("Anthropic"); + throw new APIKeyNotConfiguredException(LLMApiRegistry.Anthropic.ApiName); } } @@ -84,6 +85,7 @@ await options.TokenCallback(new LLMTokenValue() Type = TokenType.FullAnswer }); } + return CreateChatResult(chat, resultBuilder.ToString(), tokens); } @@ -155,7 +157,7 @@ private async Task ProcessWithToolsAsync( { var spaceToken = new LLMTokenValue { Text = " ", Type = TokenType.Message }; tokens.Add(spaceToken); - + if (options.TokenCallback != null) await options.TokenCallback(spaceToken); @@ -194,6 +196,7 @@ await notificationService.DispatchNotification( { fullResponseBuilder.Append(" "); } + fullResponseBuilder.Append(resultBuilder); } @@ -207,6 +210,7 @@ await notificationService.DispatchNotification( { assistantContent.Add(new { type = "text", text = resultBuilder.ToString() }); } + assistantContent.AddRange(currentToolUses.Select(tu => new { type = "tool_use", @@ -228,7 +232,7 @@ await notificationService.DispatchNotification( toolUse.Name), ServiceConstants.Notifications.ReceiveAgentUpdate); } - + var executor = chat.ToolsConfiguration?.GetExecutor(toolUse.Name); if (executor == null) @@ -328,7 +332,10 @@ await notificationService.DispatchNotification( HttpCompletionOption.ResponseHeadersRead, cancellationToken); - response.EnsureSuccessStatusCode(); + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response, cancellationToken); + } await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); using var reader = new StreamReader(stream); @@ -390,7 +397,7 @@ await notificationService.DispatchNotification( } else if (chunk.Delta?.Type == "input_json_delta") { - if (toolUseBuilders.ContainsKey(chunk.Index) && + if (toolUseBuilders.ContainsKey(chunk.Index) && !string.IsNullOrEmpty(chunk.Delta.PartialJson)) { toolUseBuilders[chunk.Index].InputJson.Append(chunk.Delta.PartialJson); @@ -416,6 +423,34 @@ await notificationService.DispatchNotification( return null; } + + private async Task HandleApiError(HttpResponseMessage response, CancellationToken cancellationToken = default) + { + var errorResponseBody = await response.Content.ReadAsStringAsync(cancellationToken); + var errorMessage = ExtractApiErrorMessage(errorResponseBody); + + throw new LLMApiException(LLMApiRegistry.Anthropic.ApiName, response.StatusCode, errorMessage ?? errorResponseBody); + } + + private static string? ExtractApiErrorMessage(string json) + { + try + { + using var jasonDocument = JsonDocument.Parse(json); + if (jasonDocument.RootElement.TryGetProperty("error", out var error) && + error.TryGetProperty("message", out var message)) + { + return message.GetString(); + } + } + catch (JsonException) + { + // If the response is not a valid JSON or doesn't match the expected schema, + // we fall back to the raw response body in the calling method. + } + + return null; + } private async Task?> ProcessNonStreamingChatWithToolsAsync( Chat chat, @@ -431,7 +466,11 @@ await notificationService.DispatchNotification( var content = new StringContent(requestJson, Encoding.UTF8, "application/json"); using var response = await httpClient.PostAsync(CompletionsUrl, content, cancellationToken); - response.EnsureSuccessStatusCode(); + + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response, cancellationToken); + } var responseJson = await response.Content.ReadAsStringAsync(cancellationToken); var chatResponse = JsonSerializer.Deserialize(responseJson, @@ -471,10 +510,10 @@ private object BuildAnthropicRequestBody(Chat chat, List conversati ["stream"] = stream, ["messages"] = BuildAnthropicMessages(conversation) }; - - var systemMessage = conversation.FirstOrDefault(m => + + var systemMessage = conversation.FirstOrDefault(m => m.Role.Equals("system", StringComparison.OrdinalIgnoreCase)); - + if (systemMessage != null && systemMessage.Content is string systemContent) { requestBody["system"] = systemContent; @@ -482,7 +521,8 @@ private object BuildAnthropicRequestBody(Chat chat, List conversati if (chat.InterferenceParams.Grammar is not null) { - requestBody["system"] = $"Respond only using the following grammar format: \n{chat.InterferenceParams.Grammar.Value}\n. Do not add explanations, code tags, or any extra content."; + requestBody["system"] = + $"Respond only using the following grammar format: \n{chat.InterferenceParams.Grammar.Value}\n. Do not add explanations, code tags, or any extra content."; } if (chat.ToolsConfiguration?.Tools != null && chat.ToolsConfiguration.Tools.Any()) @@ -506,7 +546,7 @@ private List BuildAnthropicMessages(List conversation) { if (msg.Role.Equals("system", StringComparison.OrdinalIgnoreCase)) continue; - + object content; if (msg.Content is string textContent) @@ -532,7 +572,8 @@ private List BuildAnthropicMessages(List conversation) return messages; } - public async Task AskMemory(Chat chat, ChatMemoryOptions memoryOptions, ChatRequestOptions requestOptions, CancellationToken cancellationToken = default) + public async Task AskMemory(Chat chat, ChatMemoryOptions memoryOptions, ChatRequestOptions requestOptions, + CancellationToken cancellationToken = default) { throw new NotSupportedException("Embeddings are not supported by the Anthropic. Document reading requires embedding support."); } @@ -543,10 +584,15 @@ public async Task GetCurrentModels() var httpClient = CreateAnthropicHttpClient(); using var response = await httpClient.GetAsync(ModelsUrl); - response.EnsureSuccessStatusCode(); + + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response); + } var json = await response.Content.ReadAsStringAsync(); - var modelResponse = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var modelResponse = + JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); return modelResponse?.Data?.Select(m => m.Id).ToArray() ?? []; } @@ -603,7 +649,9 @@ private async Task ProcessStreamingChatAsync( model = chat.Model, max_tokens = chat.InterferenceParams.MaxTokens < 0 ? 4096 : chat.InterferenceParams.MaxTokens, stream = true, - system = chat.InterferenceParams.Grammar is not null ? $"Respond only using the following grammar format: \n{chat.InterferenceParams.Grammar.Value}\n. Do not add explanations, code tags, or any extra content." : "", + system = chat.InterferenceParams.Grammar is not null + ? $"Respond only using the following grammar format: \n{chat.InterferenceParams.Grammar.Value}\n. Do not add explanations, code tags, or any extra content." + : "", messages = await OpenAiCompatibleService.BuildMessagesArray(conversation, chat, ImageType.AsBase64) }; @@ -620,7 +668,10 @@ private async Task ProcessStreamingChatAsync( HttpCompletionOption.ResponseHeadersRead, cancellationToken); - response.EnsureSuccessStatusCode(); + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response, cancellationToken); + } await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); using var reader = new StreamReader(stream); @@ -637,9 +688,9 @@ private async Task ProcessStreamingChatAsync( try { - var chunk = JsonSerializer.Deserialize(data, + var chunk = JsonSerializer.Deserialize(data, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - + if (chunk?.Delta?.Type == "text_delta" && !string.IsNullOrEmpty(chunk.Delta.Text)) { var token = new LLMTokenValue @@ -647,7 +698,7 @@ private async Task ProcessStreamingChatAsync( Text = chunk.Delta.Text, Type = TokenType.Message }; - + tokens.Add(token); if (tokenCallback != null) @@ -686,7 +737,9 @@ private async Task ProcessNonStreamingChatAsync( model = chat.Model, max_tokens = chat.InterferenceParams.MaxTokens < 0 ? 4096 : chat.InterferenceParams.MaxTokens, stream = false, - system = chat.InterferenceParams.Grammar is not null ? $"Respond only using the following grammar format: \n{chat.InterferenceParams.Grammar.Value}\n. Do not add explanations, code tags, or any extra content." : "", + system = chat.InterferenceParams.Grammar is not null + ? $"Respond only using the following grammar format: \n{chat.InterferenceParams.Grammar.Value}\n. Do not add explanations, code tags, or any extra content." + : "", messages = await OpenAiCompatibleService.BuildMessagesArray(conversation, chat, ImageType.AsBase64) }; @@ -694,10 +747,16 @@ private async Task ProcessNonStreamingChatAsync( var content = new StringContent(requestJson, Encoding.UTF8, "application/json"); using var response = await httpClient.PostAsync(CompletionsUrl, content, cancellationToken); - response.EnsureSuccessStatusCode(); + + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response, cancellationToken); + } var responseJson = await response.Content.ReadAsStringAsync(cancellationToken); - var chatResponse = JsonSerializer.Deserialize(responseJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var chatResponse = + JsonSerializer.Deserialize(responseJson, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); var message = chatResponse?.Content?.FirstOrDefault()?.Text; if (!string.IsNullOrWhiteSpace(message)) @@ -741,7 +800,7 @@ public AnthropicToolUse Build() { object input; var jsonString = InputJson.ToString().Trim(); - + if (string.IsNullOrWhiteSpace(jsonString)) { input = new { }; @@ -757,7 +816,7 @@ public AnthropicToolUse Build() input = new { }; } } - + return new AnthropicToolUse { Id = Id, @@ -785,13 +844,13 @@ file class AnthropicStreamChunk { [System.Text.Json.Serialization.JsonPropertyName("type")] public string? Type { get; set; } - + [System.Text.Json.Serialization.JsonPropertyName("index")] public int Index { get; set; } - + [System.Text.Json.Serialization.JsonPropertyName("delta")] public AnthropicDelta? Delta { get; set; } - + [System.Text.Json.Serialization.JsonPropertyName("content_block")] public AnthropicContentBlock? ContentBlock { get; set; } } @@ -800,13 +859,13 @@ file class AnthropicContentBlock { [System.Text.Json.Serialization.JsonPropertyName("type")] public string? Type { get; set; } - + [System.Text.Json.Serialization.JsonPropertyName("id")] public string? Id { get; set; } - + [System.Text.Json.Serialization.JsonPropertyName("name")] public string? Name { get; set; } - + [System.Text.Json.Serialization.JsonPropertyName("input")] public object? Input { get; set; } } @@ -815,10 +874,10 @@ file class AnthropicDelta { [System.Text.Json.Serialization.JsonPropertyName("type")] public string? Type { get; set; } - + [System.Text.Json.Serialization.JsonPropertyName("text")] public string? Text { get; set; } - + [System.Text.Json.Serialization.JsonPropertyName("partial_json")] public string? PartialJson { get; set; } } diff --git a/src/MaIN.Services/Services/LLMService/DeepSeekService.cs b/src/MaIN.Services/Services/LLMService/DeepSeekService.cs index 758d27d2..f632f138 100644 --- a/src/MaIN.Services/Services/LLMService/DeepSeekService.cs +++ b/src/MaIN.Services/Services/LLMService/DeepSeekService.cs @@ -10,6 +10,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using MaIN.Domain.Exceptions; +using MaIN.Services.Services.LLMService.Utils; namespace MaIN.Services.Services.LLMService; @@ -33,16 +34,18 @@ public sealed class DeepSeekService( protected override string GetApiKey() { - return _settings.DeepSeekKey ?? Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY") ?? - throw new APIKeyNotConfiguredException("DeepSeek"); + return _settings.DeepSeekKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Deepseek.ApiKeyEnvName) ?? + throw new APIKeyNotConfiguredException(LLMApiRegistry.Deepseek.ApiName); } + protected override string GetApiName() => LLMApiRegistry.Deepseek.ApiName; + protected override void ValidateApiKey() { if (string.IsNullOrEmpty(_settings.DeepSeekKey) && - string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DEEPSEEK_API_KEY"))) + string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LLMApiRegistry.Deepseek.ApiKeyEnvName))) { - throw new APIKeyNotConfiguredException("DeepSeek"); + throw new APIKeyNotConfiguredException(LLMApiRegistry.Deepseek.ApiName); } } diff --git a/src/MaIN.Services/Services/LLMService/GeminiService.cs b/src/MaIN.Services/Services/LLMService/GeminiService.cs index 9e62372a..5d741498 100644 --- a/src/MaIN.Services/Services/LLMService/GeminiService.cs +++ b/src/MaIN.Services/Services/LLMService/GeminiService.cs @@ -9,7 +9,9 @@ using System.Text.Json; using System.Text.Json.Serialization; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; using MaIN.Domain.Models; +using MaIN.Services.Services.LLMService.Utils; using MaIN.Services.Utils; namespace MaIN.Services.Services.LLMService; @@ -58,16 +60,18 @@ public override async Task GetCurrentModels() protected override string GetApiKey() { - return _settings.GeminiKey ?? Environment.GetEnvironmentVariable("GEMINI_API_KEY") ?? - throw new InvalidOperationException("Gemini Key not configured"); + return _settings.GeminiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Gemini.ApiKeyEnvName) ?? + throw new APIKeyNotConfiguredException(LLMApiRegistry.Gemini.ApiName); } + protected override string GetApiName() => LLMApiRegistry.Gemini.ApiName; + protected override void ValidateApiKey() { if (string.IsNullOrEmpty(_settings.GeminiKey) && - string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GEMINI_API_KEY"))) + string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LLMApiRegistry.Gemini.ApiKeyEnvName))) { - throw new InvalidOperationException("Gemini Key not configured"); + throw new APIKeyNotConfiguredException(LLMApiRegistry.Gemini.ApiName); } } diff --git a/src/MaIN.Services/Services/LLMService/GroqCloudService.cs b/src/MaIN.Services/Services/LLMService/GroqCloudService.cs index b04c530c..c64f6593 100644 --- a/src/MaIN.Services/Services/LLMService/GroqCloudService.cs +++ b/src/MaIN.Services/Services/LLMService/GroqCloudService.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using MaIN.Services.Services.LLMService.Memory; using MaIN.Services.Constants; +using MaIN.Services.Services.LLMService.Utils; using MaIN.Services.Services.Models; namespace MaIN.Services.Services.LLMService; @@ -27,15 +28,18 @@ public sealed class GroqCloudService( protected override string GetApiKey() { - return _settings.GroqCloudKey ?? Environment.GetEnvironmentVariable("GROQ_API_KEY") ?? - throw new APIKeyNotConfiguredException("GroqCloud"); + return _settings.GroqCloudKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Groq.ApiKeyEnvName) ?? + throw new APIKeyNotConfiguredException(LLMApiRegistry.Groq.ApiName); } + protected override string GetApiName() => LLMApiRegistry.Groq.ApiName; + protected override void ValidateApiKey() { - if (string.IsNullOrEmpty(_settings.GroqCloudKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GROQ_API_KEY"))) + if (string.IsNullOrEmpty(_settings.GroqCloudKey) && + string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LLMApiRegistry.Groq.ApiKeyEnvName))) { - throw new APIKeyNotConfiguredException("GroqCloud"); + throw new APIKeyNotConfiguredException(LLMApiRegistry.Groq.ApiName); } } diff --git a/src/MaIN.Services/Services/LLMService/LLMService.cs b/src/MaIN.Services/Services/LLMService/LLMService.cs index f45bcea7..f28b6479 100644 --- a/src/MaIN.Services/Services/LLMService/LLMService.cs +++ b/src/MaIN.Services/Services/LLMService/LLMService.cs @@ -7,6 +7,8 @@ using LLama.Sampling; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; using MaIN.Services.Constants; using MaIN.Services.Services.Abstract; @@ -436,12 +438,9 @@ private BaseSamplingPipeline CreateSampler(InferenceParams interferenceParams) private string GetModelsPath() { var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DEFAULT_MODEL_ENV_PATH); - if (string.IsNullOrEmpty(path)) - { - throw new InvalidOperationException("Models path not found in configuration or environment variables"); - } - - return path; + return string.IsNullOrEmpty(path) + ? throw new ModelsPathNotFoundException() + : path; } private async Task CreateChatResult(Chat chat, List tokens, diff --git a/src/MaIN.Services/Services/LLMService/Memory/Embeddings/LLamaSharpTextEmbeddingMaINClone.cs b/src/MaIN.Services/Services/LLMService/Memory/Embeddings/LLamaSharpTextEmbeddingMaINClone.cs index 93716eaa..b8b99d44 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/Embeddings/LLamaSharpTextEmbeddingMaINClone.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/Embeddings/LLamaSharpTextEmbeddingMaINClone.cs @@ -1,13 +1,13 @@ +using System.Text; using LLama; +using LLama.Abstractions; using LLama.Common; using LLama.Native; +using LLamaSharp.KernelMemory; using Microsoft.KernelMemory; using Microsoft.KernelMemory.AI; -using System.Text; -using LLama.Abstractions; -using MaIN.Services.Services.LLMService.Memory.Embeddings; -namespace LLamaSharp.KernelMemory +namespace MaIN.Services.Services.LLMService.Memory.Embeddings { /// /// Provides text embedding generation for LLamaSharp. - Clone in MaIN.Package to support embeddings in custom manner diff --git a/src/MaIN.Services/Services/LLMService/Memory/IMemoryFactory.cs b/src/MaIN.Services/Services/LLMService/Memory/IMemoryFactory.cs index e23ca8bc..97aa040d 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/IMemoryFactory.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/IMemoryFactory.cs @@ -2,6 +2,7 @@ using LLama; using LLamaSharp.KernelMemory; using MaIN.Domain.Entities; +using MaIN.Services.Services.LLMService.Memory.Embeddings; using Microsoft.KernelMemory; namespace MaIN.Services.Services.LLMService.Memory; diff --git a/src/MaIN.Services/Services/LLMService/Memory/KernelMemoryLlamaExtensions.cs b/src/MaIN.Services/Services/LLMService/Memory/KernelMemoryLlamaExtensions.cs index d16141d5..262a4f08 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/KernelMemoryLlamaExtensions.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/KernelMemoryLlamaExtensions.cs @@ -4,6 +4,7 @@ using LLama.Sampling; using LLamaSharp.KernelMemory; using MaIN.Domain.Entities; +using MaIN.Services.Services.LLMService.Memory.Embeddings; using Microsoft.KernelMemory; using Microsoft.KernelMemory.AI; using InferenceParams = LLama.Common.InferenceParams; diff --git a/src/MaIN.Services/Services/LLMService/Memory/MemoryFactory.cs b/src/MaIN.Services/Services/LLMService/Memory/MemoryFactory.cs index a7ff70a7..7a027e05 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/MemoryFactory.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/MemoryFactory.cs @@ -3,7 +3,10 @@ using LLama.Common; using LLamaSharp.KernelMemory; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; +using MaIN.Services.Services.LLMService.Memory.Embeddings; using Microsoft.KernelMemory; using Microsoft.KernelMemory.Configuration; using Microsoft.KernelMemory.SemanticKernel; @@ -83,12 +86,9 @@ private string ResolvePath(string modelsPath) { var path = modelsPath; - if (string.IsNullOrEmpty(path)) - { - throw new InvalidOperationException("Models path not found"); - } - - return path; + return string.IsNullOrEmpty(path) + ? throw new ModelsPathNotFoundException() + : path; } private static LLamaSharpTextEmbeddingMaINClone ConfigureGeneratorOptions(string embeddingModelPath, diff --git a/src/MaIN.Services/Services/LLMService/Memory/MemoryService.cs b/src/MaIN.Services/Services/LLMService/Memory/MemoryService.cs index bcc51c0a..2dda0dcc 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/MemoryService.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/MemoryService.cs @@ -1,5 +1,7 @@ using LLama.Native; using LLamaSharp.KernelMemory; +using MaIN.Services.Services.LLMService.Memory.Embeddings; +using MaIN.Services.Utils; using Microsoft.KernelMemory; using Microsoft.KernelMemory.AI; diff --git a/src/MaIN.Services/Services/LLMService/OpenAiCompatibleService.cs b/src/MaIN.Services/Services/LLMService/OpenAiCompatibleService.cs index e2b527e9..feeb26b9 100644 --- a/src/MaIN.Services/Services/LLMService/OpenAiCompatibleService.cs +++ b/src/MaIN.Services/Services/LLMService/OpenAiCompatibleService.cs @@ -16,6 +16,7 @@ using MaIN.Services.Services.LLMService.Memory; using LLama.Common; using MaIN.Domain.Entities.Tools; +using MaIN.Domain.Exceptions; namespace MaIN.Services.Services.LLMService; @@ -43,6 +44,7 @@ public abstract class OpenAiCompatibleService( private const string ToolNameProperty = "ToolName"; protected abstract string GetApiKey(); + protected abstract string GetApiName(); protected abstract void ValidateApiKey(); protected virtual string HttpClientName => ServiceConstants.HttpClients.OpenAiClient; protected virtual string ChatCompletionsUrl => ServiceConstants.ApiUrls.OpenAiChatCompletions; @@ -309,7 +311,10 @@ await _notificationService.DispatchNotification( HttpCompletionOption.ResponseHeadersRead, cancellationToken); - response.EnsureSuccessStatusCode(); + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response, cancellationToken); + } await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); using var reader = new StreamReader(stream); @@ -419,7 +424,11 @@ await _notificationService.DispatchNotification( var content = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await client.PostAsync(ChatCompletionsUrl, content, cancellationToken); - response.EnsureSuccessStatusCode(); + + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response, cancellationToken); + } var responseJson = await response.Content.ReadAsStringAsync(cancellationToken); var chatResponse = JsonSerializer.Deserialize( @@ -527,7 +536,11 @@ public virtual async Task GetCurrentModels() GetApiKey()); using var response = await client.GetAsync(ModelsUrl); - response.EnsureSuccessStatusCode(); + + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response); + } var responseJson = await response.Content.ReadAsStringAsync(); var modelsResponse = JsonSerializer.Deserialize(responseJson, @@ -602,7 +615,10 @@ private async Task ProcessStreamingChatAsync( HttpCompletionOption.ResponseHeadersRead, cancellationToken); - response.EnsureSuccessStatusCode(); + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response, cancellationToken); + } await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); using var reader = new StreamReader(stream); @@ -649,6 +665,34 @@ await _notificationService.DispatchNotification( } } + private async Task HandleApiError(HttpResponseMessage response, CancellationToken cancellationToken = default) + { + var errorResponseBody = await response.Content.ReadAsStringAsync(cancellationToken); + var errorMessage = ExtractApiErrorMessage(errorResponseBody); + + throw new LLMApiException(GetApiName(), response.StatusCode, errorMessage ?? errorResponseBody); + } + + private static string? ExtractApiErrorMessage(string json) + { + try + { + using var jasonDocument = JsonDocument.Parse(json); + if (jasonDocument.RootElement.TryGetProperty("error", out var error) && + error.TryGetProperty("message", out var message)) + { + return message.GetString(); + } + } + catch (JsonException) + { + // If the response is not a valid JSON or doesn't match the expected schema, + // we fall back to the raw response body in the calling method. + } + + return null; + } + protected virtual LLMTokenValue? ProcessChatCompletionChunk(string data) { var chunk = JsonSerializer.Deserialize(data, @@ -678,7 +722,11 @@ private async Task ProcessNonStreamingChatAsync( var content = new StringContent(requestJson, Encoding.UTF8, MediaTypeNames.Application.Json); using var response = await client.PostAsync(ChatCompletionsUrl, content, cancellationToken); - response.EnsureSuccessStatusCode(); + + if (!response.IsSuccessStatusCode) + { + await HandleApiError(response, cancellationToken); + } var responseJson = await response.Content.ReadAsStringAsync(cancellationToken); var chatResponse = diff --git a/src/MaIN.Services/Services/LLMService/OpenAiService.cs b/src/MaIN.Services/Services/LLMService/OpenAiService.cs index 72991bb6..e64c9fd9 100644 --- a/src/MaIN.Services/Services/LLMService/OpenAiService.cs +++ b/src/MaIN.Services/Services/LLMService/OpenAiService.cs @@ -3,6 +3,7 @@ using MaIN.Services.Services.Abstract; using Microsoft.Extensions.Logging; using MaIN.Services.Services.LLMService.Memory; +using MaIN.Services.Services.LLMService.Utils; namespace MaIN.Services.Services.LLMService; @@ -19,15 +20,17 @@ public sealed class OpenAiService( protected override string GetApiKey() { - return _settings.OpenAiKey ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? - throw new APIKeyNotConfiguredException("OpenAi"); + return _settings.OpenAiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.OpenAi.ApiKeyEnvName) ?? + throw new APIKeyNotConfiguredException(LLMApiRegistry.OpenAi.ApiName); } + protected override string GetApiName() => LLMApiRegistry.OpenAi.ApiName; + protected override void ValidateApiKey() { - if (string.IsNullOrEmpty(_settings.OpenAiKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))) + if (string.IsNullOrEmpty(_settings.OpenAiKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LLMApiRegistry.OpenAi.ApiKeyEnvName))) { - throw new APIKeyNotConfiguredException("OpenAi"); + throw new APIKeyNotConfiguredException(LLMApiRegistry.OpenAi.ApiName); } } diff --git a/src/MaIN.Services/Services/LLMService/Utils/LLMApiRegistry.cs b/src/MaIN.Services/Services/LLMService/Utils/LLMApiRegistry.cs new file mode 100644 index 00000000..bd69076e --- /dev/null +++ b/src/MaIN.Services/Services/LLMService/Utils/LLMApiRegistry.cs @@ -0,0 +1,13 @@ +namespace MaIN.Services.Services.LLMService.Utils; + +public static class LLMApiRegistry +{ + public static readonly LLMApiRegistryEntry OpenAi = new("OpenAI", "OPENAI_API_KEY"); + public static readonly LLMApiRegistryEntry Gemini = new("Gemini", "GEMINI_API_KEY"); + public static readonly LLMApiRegistryEntry Deepseek = new("Deepseek", "DEEPSEEK_API_KEY"); + public static readonly LLMApiRegistryEntry Groq = new("GroqCloud", "GROQ_API_KEY"); + public static readonly LLMApiRegistryEntry Anthropic = new("Anthropic", "ANTHROPIC_API_KEY"); + public static readonly LLMApiRegistryEntry Xai = new("xAI", "XAI_API_KEY"); +} + +public record LLMApiRegistryEntry(string ApiName, string ApiKeyEnvName); \ No newline at end of file diff --git a/src/MaIN.Services/Services/LLMService/XaiService.cs b/src/MaIN.Services/Services/LLMService/XaiService.cs index 9d7095f7..d61f9d18 100644 --- a/src/MaIN.Services/Services/LLMService/XaiService.cs +++ b/src/MaIN.Services/Services/LLMService/XaiService.cs @@ -6,6 +6,8 @@ using MaIN.Services.Services.LLMService.Memory; using Microsoft.Extensions.Logging; using System.Text; +using MaIN.Domain.Exceptions; +using MaIN.Services.Services.LLMService.Utils; namespace MaIN.Services.Services.LLMService; @@ -26,15 +28,17 @@ public sealed class XaiService( protected override string GetApiKey() { - return _settings.XaiKey ?? Environment.GetEnvironmentVariable("XAI_API_KEY") ?? - throw new InvalidOperationException("xAI Key not configured"); + return _settings.XaiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Xai.ApiKeyEnvName) ?? + throw new APIKeyNotConfiguredException(LLMApiRegistry.Xai.ApiName); } + + protected override string GetApiName() => LLMApiRegistry.Xai.ApiName; protected override void ValidateApiKey() { - if (string.IsNullOrEmpty(_settings.XaiKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("XAI_API_KEY"))) + if (string.IsNullOrEmpty(_settings.XaiKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable(LLMApiRegistry.Xai.ApiKeyEnvName))) { - throw new InvalidOperationException("xAI Key not configured"); + throw new APIKeyNotConfiguredException(LLMApiRegistry.Xai.ApiName); } } diff --git a/src/MaIN.Services/Services/McpService.cs b/src/MaIN.Services/Services/McpService.cs index 06de96ba..c7e94317 100644 --- a/src/MaIN.Services/Services/McpService.cs +++ b/src/MaIN.Services/Services/McpService.cs @@ -126,13 +126,13 @@ private PromptExecutionSettings InitializeChatCompletions(IKernelBuilder kernelB } string? GetOpenAiKey() - => settings.OpenAiKey ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + => settings.OpenAiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.OpenAi.ApiKeyEnvName); string? GetGeminiKey() - => settings.GeminiKey ?? Environment.GetEnvironmentVariable("GEMINI_API_KEY"); + => settings.GeminiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Gemini.ApiKeyEnvName); string? GetGroqCloudKey() - => settings.GroqCloudKey ?? Environment.GetEnvironmentVariable("GROQ_API_KEY"); + => settings.GroqCloudKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Groq.ApiKeyEnvName); string? GetAnthropicKey() - => settings.AnthropicKey ?? Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"); + => settings.AnthropicKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Anthropic.ApiKeyEnvName); string? GetXaiKey() - => settings.XaiKey ?? Environment.GetEnvironmentVariable("XAI_API_KEY"); + => settings.XaiKey ?? Environment.GetEnvironmentVariable(LLMApiRegistry.Xai.ApiKeyEnvName); } \ No newline at end of file diff --git a/src/MaIN.Services/Services/Models/Commands/AnswerCommand.cs b/src/MaIN.Services/Services/Models/Commands/AnswerCommand.cs index 27b63915..40523e08 100644 --- a/src/MaIN.Services/Services/Models/Commands/AnswerCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/AnswerCommand.cs @@ -5,6 +5,7 @@ using MaIN.Services.Constants; using MaIN.Services.Services.Models.Commands.Base; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/Commands/FetchCommand.cs b/src/MaIN.Services/Services/Models/Commands/FetchCommand.cs index 8dcfcbcf..3ba5b4c1 100644 --- a/src/MaIN.Services/Services/Models/Commands/FetchCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/FetchCommand.cs @@ -2,6 +2,7 @@ using MaIN.Domain.Entities.Agents; using MaIN.Services.Services.Models.Commands.Base; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/Commands/McpCommand.cs b/src/MaIN.Services/Services/Models/Commands/McpCommand.cs index 858f74d8..78578650 100644 --- a/src/MaIN.Services/Services/Models/Commands/McpCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/McpCommand.cs @@ -1,6 +1,7 @@ using MaIN.Domain.Entities; using MaIN.Services.Services.Models.Commands.Base; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/Commands/RedirectCommand.cs b/src/MaIN.Services/Services/Models/Commands/RedirectCommand.cs index d2ea940f..a8a4bfe3 100644 --- a/src/MaIN.Services/Services/Models/Commands/RedirectCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/RedirectCommand.cs @@ -1,6 +1,7 @@ using MaIN.Domain.Entities; using MaIN.Services.Services.Models.Commands.Base; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/Commands/StartCommand.cs b/src/MaIN.Services/Services/Models/Commands/StartCommand.cs index e5e53e26..5a424b55 100644 --- a/src/MaIN.Services/Services/Models/Commands/StartCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/StartCommand.cs @@ -1,6 +1,7 @@ using MaIN.Domain.Entities; using MaIN.Services.Services.Models.Commands.Base; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs b/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs index 6658a526..32665182 100644 --- a/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs @@ -7,6 +7,7 @@ using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps; diff --git a/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommand.cs b/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommand.cs index 0f79c84e..ca09739b 100644 --- a/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommand.cs +++ b/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommand.cs @@ -1,4 +1,4 @@ -namespace MaIN.Services.Services.Steps.Commands; +namespace MaIN.Services.Services.Steps.Commands.Abstract; public interface ICommand { diff --git a/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommandDispatcher.cs b/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommandDispatcher.cs index 322a20e0..fe3bc35b 100644 --- a/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommandDispatcher.cs +++ b/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommandDispatcher.cs @@ -1,4 +1,4 @@ -namespace MaIN.Services.Services.Steps.Commands; +namespace MaIN.Services.Services.Steps.Commands.Abstract; public interface ICommandDispatcher { diff --git a/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommandHandler.cs b/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommandHandler.cs index 88cc3fb7..bd13f0c1 100644 --- a/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommandHandler.cs +++ b/src/MaIN.Services/Services/Steps/Commands/Abstract/ICommandHandler.cs @@ -1,4 +1,4 @@ -namespace MaIN.Services.Services.Steps.Commands; +namespace MaIN.Services.Services.Steps.Commands.Abstract; public interface ICommandHandler where TCommand : ICommand { diff --git a/src/MaIN.Services/Services/Steps/Commands/AnswerCommandHandler.cs b/src/MaIN.Services/Services/Steps/Commands/AnswerCommandHandler.cs index 9dc68097..6913a8bd 100644 --- a/src/MaIN.Services/Services/Steps/Commands/AnswerCommandHandler.cs +++ b/src/MaIN.Services/Services/Steps/Commands/AnswerCommandHandler.cs @@ -9,6 +9,7 @@ using MaIN.Services.Services.LLMService.Factory; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; using MaIN.Services.Utils; namespace MaIN.Services.Services.Steps.Commands; diff --git a/src/MaIN.Services/Services/Steps/Commands/CommandDispatcher.cs b/src/MaIN.Services/Services/Steps/Commands/CommandDispatcher.cs index 6dd33cb8..f3d29339 100644 --- a/src/MaIN.Services/Services/Steps/Commands/CommandDispatcher.cs +++ b/src/MaIN.Services/Services/Steps/Commands/CommandDispatcher.cs @@ -1,4 +1,6 @@ +using MaIN.Services.Services.Steps.Commands.Abstract; + namespace MaIN.Services.Services.Steps.Commands; diff --git a/src/MaIN.Services/Services/Steps/Commands/FetchCommandHandler.cs b/src/MaIN.Services/Services/Steps/Commands/FetchCommandHandler.cs index 5818614c..6ec74959 100644 --- a/src/MaIN.Services/Services/Steps/Commands/FetchCommandHandler.cs +++ b/src/MaIN.Services/Services/Steps/Commands/FetchCommandHandler.cs @@ -6,6 +6,7 @@ using MaIN.Domain.Configuration; using MaIN.Services.Services.LLMService; using MaIN.Services.Services.LLMService.Factory; +using MaIN.Services.Services.Steps.Commands.Abstract; using MaIN.Services.Utils; namespace MaIN.Services.Services.Steps.Commands; diff --git a/src/MaIN.Services/Services/Steps/Commands/McpCommandHandler.cs b/src/MaIN.Services/Services/Steps/Commands/McpCommandHandler.cs index 1b17bfbe..21e0af2f 100644 --- a/src/MaIN.Services/Services/Steps/Commands/McpCommandHandler.cs +++ b/src/MaIN.Services/Services/Steps/Commands/McpCommandHandler.cs @@ -7,6 +7,7 @@ using MaIN.Services.Services.LLMService.Factory; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps.Commands; diff --git a/src/MaIN.Services/Services/Steps/Commands/RedirectCommandHandler.cs b/src/MaIN.Services/Services/Steps/Commands/RedirectCommandHandler.cs index 64d2b11b..c3a8473b 100644 --- a/src/MaIN.Services/Services/Steps/Commands/RedirectCommandHandler.cs +++ b/src/MaIN.Services/Services/Steps/Commands/RedirectCommandHandler.cs @@ -3,6 +3,7 @@ using MaIN.Services.Constants; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps.Commands; diff --git a/src/MaIN.Services/Services/Steps/Commands/StartCommandHandler.cs b/src/MaIN.Services/Services/Steps/Commands/StartCommandHandler.cs index 64a4e7a5..d74b89d4 100644 --- a/src/MaIN.Services/Services/Steps/Commands/StartCommandHandler.cs +++ b/src/MaIN.Services/Services/Steps/Commands/StartCommandHandler.cs @@ -1,6 +1,7 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Services.Services.Models.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps.Commands; diff --git a/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs b/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs index f8745d32..6a3991ea 100644 --- a/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs @@ -6,6 +6,7 @@ using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps; diff --git a/src/MaIN.Services/Services/Steps/McpStepHandler.cs b/src/MaIN.Services/Services/Steps/McpStepHandler.cs index 24dc4d5c..7078cb5d 100644 --- a/src/MaIN.Services/Services/Steps/McpStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/McpStepHandler.cs @@ -2,10 +2,12 @@ using DocumentFormat.OpenXml.Wordprocessing; using MaIN.Domain.Entities; using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.MPC; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; using MaIN.Services.Services.Steps.Commands; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps; diff --git a/src/MaIN.Services/Services/Steps/RedirectStepHandler.cs b/src/MaIN.Services/Services/Steps/RedirectStepHandler.cs index ab5e5007..7e6c1469 100644 --- a/src/MaIN.Services/Services/Steps/RedirectStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/RedirectStepHandler.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using MaIN.Domain.Entities; +using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps; diff --git a/src/MaIN.Services/Services/TTSService/TextToSpeechService.cs b/src/MaIN.Services/Services/TTSService/TextToSpeechService.cs index bc53f149..e7315112 100644 --- a/src/MaIN.Services/Services/TTSService/TextToSpeechService.cs +++ b/src/MaIN.Services/Services/TTSService/TextToSpeechService.cs @@ -1,5 +1,7 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; +using MaIN.Domain.Exceptions; +using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; using NAudio.Wave; @@ -98,11 +100,8 @@ private async Task PlaybackAudio(byte[] audioData) private string GetModelsPath() { var path = options.ModelsPath ?? Environment.GetEnvironmentVariable("MaIN_ModelsPath"); - if (string.IsNullOrEmpty(path)) - { - throw new InvalidOperationException("Models path not found in configuration or environment variables"); - } - - return path; + return string.IsNullOrEmpty(path) + ? throw new ModelsPathNotFoundException() + : path; } } \ No newline at end of file diff --git a/src/MaIN.Services/Utils/JsonCleaner.cs b/src/MaIN.Services/Utils/JsonCleaner.cs index bb2c2408..5404a3bf 100644 --- a/src/MaIN.Services/Utils/JsonCleaner.cs +++ b/src/MaIN.Services/Utils/JsonCleaner.cs @@ -1,8 +1,8 @@ -using MaIN.Domain.Models; -using System; -using System.Text.Encodings.Web; +using System.Text.Encodings.Web; using System.Text.Json; +namespace MaIN.Services.Utils; + public static class JsonCleaner { public static string? CleanAndUnescape(string json, int maxDepth = 5) From d17fa45a66357c66e9b4a1b061bf22cd0e4017f8 Mon Sep 17 00:00:00 2001 From: Szymon Gurak Date: Sun, 18 Jan 2026 17:49:17 +0100 Subject: [PATCH 8/9] [#19] Reorganize the FluentApi aproach for ChatContext, AgentContext and ensure validation --- Examples/Examples.SimpleConsole/Program.cs | 2 - Examples/Examples/Agents/AgentExample.cs | 2 - .../Agents/AgentWithApiDataSourceExample.cs | 1 - .../Agents/AgentWithKnowledgeFileExample.cs | 3 - .../Agents/AgentWithKnowledgeWebExample.cs | 3 - .../AgentWithWebDataSourceOpenAiExample.cs | 1 - .../MultiBackendAgentsWithRedirectExample.cs | 2 +- .../Examples/Chat/ChatCustomGrammarExample.cs | 4 +- .../Examples/Chat/ChatExampleAnthropic.cs | 1 - .../Examples/Chat/ChatExampleToolsSimple.cs | 1 - .../Examples/Chat/ChatFromExistingExample.cs | 1 - .../Examples/Chat/ChatGrammarExampleGemini.cs | 14 +- .../Chat/ChatWithImageGenOpenAiExample.cs | 2 +- .../Mcp/AgentWithKnowledgeMcpExample.cs | 2 +- Examples/Examples/Mcp/McpAgentsExample.cs | 2 +- Examples/Examples/Utils/Tools.cs | 2 - MaIN.Core.IntegrationTests/ChatTests.cs | 3 +- .../IntegrationTestBase.cs | 4 - src/MaIN.Core.UnitTests/AgentContextTests.cs | 1 - src/MaIN.Core.UnitTests/ChatContextTests.cs | 1 - src/MaIN.Core/Hub/AiHub.cs | 2 +- src/MaIN.Core/Hub/Contexts/AgentContext.cs | 139 ++++++------- src/MaIN.Core/Hub/Contexts/ChatContext.cs | 187 ++++++++---------- src/MaIN.Core/Hub/Contexts/FlowContext.cs | 3 - .../Interfaces/AgentContext/IAgentActions.cs | 62 ++++++ .../AgentContext/IAgentBuilderEntryPoint.cs | 25 +++ .../IAgentConfigurationBuilder.cs | 128 ++++++++++++ .../AgentContext/IAgentContextExecutor.cs | 47 +++++ .../Interfaces/ChatContext/IChatActions.cs | 31 +++ .../ChatContext/IChatBuilderEntryPoint.cs | 30 +++ .../ChatContext/IChatCompletionBuilder.cs | 83 ++++++++ .../ChatContext/IChatMessageBuilder.cs | 30 +++ src/MaIN.Core/Hub/Contexts/McpContext.cs | 1 - src/MaIN.Core/Hub/Contexts/ModelContext.cs | 1 - .../Entities/Agents/Knowledge/Knowledge.cs | 1 - src/MaIN.Domain/Models/SupportedModels.cs | 1 - .../Components/Layout/MainLayout.razor | 3 +- .../Components/Layout/NavBar.razor | 1 - .../Components/Pages/Home.razor | 10 +- .../DefaultAgentFlowRepository.cs | 1 - .../Repositories/DefaultAgentRepository.cs | 1 - .../Repositories/DefaultChatRepository.cs | 1 - .../FileSystemAgentFlowRepository.cs | 1 - .../FileSystem/FileSystemAgentRepository.cs | 1 - .../FileSystem/FileSystemChatRepository.cs | 1 - src/MaIN.Services/Mappers/ChatMapper.cs | 1 - .../Services/Abstract/IChatService.cs | 1 - .../Services/Abstract/IImageGenService.cs | 1 - .../Services/Abstract/ILLMService.cs | 2 - .../Services/AgentFlowService.cs | 2 - src/MaIN.Services/Services/AgentService.cs | 2 - src/MaIN.Services/Services/ChatService.cs | 1 - .../ImageGenServices/ImageGenService.cs | 4 - .../Services/LLMService/AnthropicService.cs | 1 - .../Services/LLMService/LLMService.cs | 1 - .../LLMService/Memory/IMemoryFactory.cs | 1 - .../LLMService/Memory/IMemoryService.cs | 1 - .../Memory/KernelMemoryLlamaExtensions.cs | 1 - .../LLMService/Memory/MemoryFactory.cs | 1 - .../LLMService/Memory/MemoryService.cs | 1 - .../Services/Models/ChatResult.cs | 2 - .../Services/Models/Commands/AnswerCommand.cs | 1 - .../Services/Models/Commands/FetchCommand.cs | 1 - .../Services/Models/Commands/McpCommand.cs | 1 - .../Models/Commands/RedirectCommand.cs | 1 - .../Services/Models/Commands/StartCommand.cs | 1 - .../Services/Models/McpResult.cs | 2 - .../Services/Steps/AnswerStepHandler.cs | 3 - .../Steps/Commands/McpCommandHandler.cs | 6 - .../Steps/Commands/RedirectCommandHandler.cs | 1 - .../Services/Steps/FechDataStepHandler.cs | 1 - .../Services/Steps/McpStepHandler.cs | 3 - .../Services/Steps/RedirectStepHandler.cs | 4 - .../TTSService/TextToSpeechService.cs | 1 - 74 files changed, 608 insertions(+), 284 deletions(-) create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentActions.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentBuilderEntryPoint.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentConfigurationBuilder.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentContextExecutor.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatActions.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatBuilderEntryPoint.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatCompletionBuilder.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs diff --git a/Examples/Examples.SimpleConsole/Program.cs b/Examples/Examples.SimpleConsole/Program.cs index 3ace6527..8662cdcd 100644 --- a/Examples/Examples.SimpleConsole/Program.cs +++ b/Examples/Examples.SimpleConsole/Program.cs @@ -1,7 +1,5 @@ using MaIN.Core; using MaIN.Core.Hub; -using MaIN.Domain.Entities; -using OpenAI.Models; MaINBootstrapper.Initialize(); diff --git a/Examples/Examples/Agents/AgentExample.cs b/Examples/Examples/Agents/AgentExample.cs index 2f9764a5..3678efad 100644 --- a/Examples/Examples/Agents/AgentExample.cs +++ b/Examples/Examples/Agents/AgentExample.cs @@ -1,6 +1,4 @@ using MaIN.Core.Hub; -using MaIN.Core.Hub.Utils; -using MaIN.Domain.Entities.Agents.Knowledge; namespace Examples.Agents; diff --git a/Examples/Examples/Agents/AgentWithApiDataSourceExample.cs b/Examples/Examples/Agents/AgentWithApiDataSourceExample.cs index c94ae8cb..b2a05fb6 100644 --- a/Examples/Examples/Agents/AgentWithApiDataSourceExample.cs +++ b/Examples/Examples/Agents/AgentWithApiDataSourceExample.cs @@ -1,7 +1,6 @@ using MaIN.Core.Hub; using MaIN.Core.Hub.Utils; using MaIN.Domain.Entities.Agents.AgentSource; -using MaIN.Services.Services.Models.Commands; namespace Examples.Agents; diff --git a/Examples/Examples/Agents/AgentWithKnowledgeFileExample.cs b/Examples/Examples/Agents/AgentWithKnowledgeFileExample.cs index 9bb72aa4..ffd55025 100644 --- a/Examples/Examples/Agents/AgentWithKnowledgeFileExample.cs +++ b/Examples/Examples/Agents/AgentWithKnowledgeFileExample.cs @@ -1,8 +1,5 @@ -using Examples.Utils; using MaIN.Core.Hub; using MaIN.Core.Hub.Utils; -using MaIN.Domain.Entities.Agents.AgentSource; -using Microsoft.Identity.Client; namespace Examples.Agents; diff --git a/Examples/Examples/Agents/AgentWithKnowledgeWebExample.cs b/Examples/Examples/Agents/AgentWithKnowledgeWebExample.cs index 29027c6b..8844e061 100644 --- a/Examples/Examples/Agents/AgentWithKnowledgeWebExample.cs +++ b/Examples/Examples/Agents/AgentWithKnowledgeWebExample.cs @@ -1,9 +1,6 @@ -using Examples.Utils; using MaIN.Core.Hub; using MaIN.Core.Hub.Utils; using MaIN.Domain.Entities; -using MaIN.Domain.Entities.Agents.AgentSource; -using Microsoft.Identity.Client; namespace Examples.Agents; diff --git a/Examples/Examples/Agents/AgentWithWebDataSourceOpenAiExample.cs b/Examples/Examples/Agents/AgentWithWebDataSourceOpenAiExample.cs index 8d8e7769..a1d0b585 100644 --- a/Examples/Examples/Agents/AgentWithWebDataSourceOpenAiExample.cs +++ b/Examples/Examples/Agents/AgentWithWebDataSourceOpenAiExample.cs @@ -1,7 +1,6 @@ using Examples.Utils; using MaIN.Core.Hub; using MaIN.Core.Hub.Utils; -using MaIN.Domain.Configuration; using MaIN.Domain.Entities.Agents.AgentSource; namespace Examples.Agents; diff --git a/Examples/Examples/Agents/MultiBackendAgentsWithRedirectExample.cs b/Examples/Examples/Agents/MultiBackendAgentsWithRedirectExample.cs index ba4eeeab..07b4d8b8 100644 --- a/Examples/Examples/Agents/MultiBackendAgentsWithRedirectExample.cs +++ b/Examples/Examples/Agents/MultiBackendAgentsWithRedirectExample.cs @@ -26,9 +26,9 @@ You need to use a lot of it. Imagine you are the voice of youth. """; var contextSecond = await AIHub.Agent() - .WithBackend(BackendType.OpenAi) .WithModel("gpt-4o") .WithInitialPrompt(systemPromptSecond) + .WithBackend(BackendType.OpenAi) .CreateAsync(interactiveResponse: true); var context = await AIHub.Agent() diff --git a/Examples/Examples/Chat/ChatCustomGrammarExample.cs b/Examples/Examples/Chat/ChatCustomGrammarExample.cs index ee812e03..ec58c25c 100644 --- a/Examples/Examples/Chat/ChatCustomGrammarExample.cs +++ b/Examples/Examples/Chat/ChatCustomGrammarExample.cs @@ -21,12 +21,12 @@ public async Task Start() """, GrammarFormat.GBNF); await AIHub.Chat() + .WithModel("gemma2:2b") + .WithMessage("Generate random person") .WithInferenceParams(new InferenceParams { Grammar = personGrammar }) - .WithModel("gemma2:2b") - .WithMessage("Generate random person") .CompleteAsync(interactive: true); } } \ No newline at end of file diff --git a/Examples/Examples/Chat/ChatExampleAnthropic.cs b/Examples/Examples/Chat/ChatExampleAnthropic.cs index 1e2315e6..b24baf59 100644 --- a/Examples/Examples/Chat/ChatExampleAnthropic.cs +++ b/Examples/Examples/Chat/ChatExampleAnthropic.cs @@ -1,6 +1,5 @@ using Examples.Utils; using MaIN.Core.Hub; -using MaIN.Domain.Configuration; namespace Examples.Chat; diff --git a/Examples/Examples/Chat/ChatExampleToolsSimple.cs b/Examples/Examples/Chat/ChatExampleToolsSimple.cs index 9e0226d1..d3a6e375 100644 --- a/Examples/Examples/Chat/ChatExampleToolsSimple.cs +++ b/Examples/Examples/Chat/ChatExampleToolsSimple.cs @@ -1,7 +1,6 @@ using Examples.Utils; using MaIN.Core.Hub; using MaIN.Core.Hub.Utils; -using MaIN.Domain.Configuration; namespace Examples.Chat; diff --git a/Examples/Examples/Chat/ChatFromExistingExample.cs b/Examples/Examples/Chat/ChatFromExistingExample.cs index 416561a4..adeba5a0 100644 --- a/Examples/Examples/Chat/ChatFromExistingExample.cs +++ b/Examples/Examples/Chat/ChatFromExistingExample.cs @@ -1,6 +1,5 @@ using System.Text.Json; using MaIN.Core.Hub; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Chats; namespace Examples.Chat; diff --git a/Examples/Examples/Chat/ChatGrammarExampleGemini.cs b/Examples/Examples/Chat/ChatGrammarExampleGemini.cs index ba1a9f42..43ee0ebb 100644 --- a/Examples/Examples/Chat/ChatGrammarExampleGemini.cs +++ b/Examples/Examples/Chat/ChatGrammarExampleGemini.cs @@ -38,12 +38,12 @@ public async Task Start() """; await AIHub.Chat() - .WithInferenceParams(new InferenceParams - { - Grammar = new Grammar(grammarValue, GrammarFormat.JSONSchema) - }) - .WithModel("gemini-2.5-flash") - .WithMessage("Generate random person") - .CompleteAsync(interactive: true); + .WithModel("gemini-2.5-flash") + .WithMessage("Generate random person") + .WithInferenceParams(new InferenceParams + { + Grammar = new Grammar(grammarValue, GrammarFormat.JSONSchema) + }) + .CompleteAsync(interactive: true); } } \ No newline at end of file diff --git a/Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs b/Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs index 1edef812..3f3529c3 100644 --- a/Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs +++ b/Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs @@ -11,8 +11,8 @@ public async Task Start() OpenAiExample.Setup(); // We need to provide OpenAi API key var result = await AIHub.Chat() - .EnableVisual() .WithModel("dall-e-3") + .EnableVisual() .WithMessage("Generate rock style cow playing guitar") .CompleteAsync(); diff --git a/Examples/Examples/Mcp/AgentWithKnowledgeMcpExample.cs b/Examples/Examples/Mcp/AgentWithKnowledgeMcpExample.cs index b836b3f6..a9a3c544 100644 --- a/Examples/Examples/Mcp/AgentWithKnowledgeMcpExample.cs +++ b/Examples/Examples/Mcp/AgentWithKnowledgeMcpExample.cs @@ -14,8 +14,8 @@ public async Task Start() AIHub.Extensions.DisableLLamaLogs(); var context = await AIHub.Agent() - .WithBackend(BackendType.OpenAi) .WithModel("gpt-4.1-mini") + .WithBackend(BackendType.OpenAi) .WithKnowledge(KnowledgeBuilder.Instance .AddMcp(new MaIN.Domain.Entities.Mcp { diff --git a/Examples/Examples/Mcp/McpAgentsExample.cs b/Examples/Examples/Mcp/McpAgentsExample.cs index fac050ba..44ee7f81 100644 --- a/Examples/Examples/Mcp/McpAgentsExample.cs +++ b/Examples/Examples/Mcp/McpAgentsExample.cs @@ -17,6 +17,7 @@ public async Task Start() .CreateAsync(interactiveResponse: true); var context = await AIHub.Agent() + .WithModel("gpt-4o-mini") .WithBackend(BackendType.OpenAi) .WithMcpConfig(new MaIN.Domain.Entities.Mcp { @@ -29,7 +30,6 @@ public async Task Start() Command = "docker", Model = "gpt-4o-mini" }) - .WithModel("gpt-4o-mini") .WithSteps(StepBuilder.Instance .Mcp() .Redirect(agentId: contextSecond.GetAgentId()) diff --git a/Examples/Examples/Utils/Tools.cs b/Examples/Examples/Utils/Tools.cs index 166413e7..89033ae3 100644 --- a/Examples/Examples/Utils/Tools.cs +++ b/Examples/Examples/Utils/Tools.cs @@ -1,5 +1,3 @@ -using System.Text.Json; - namespace Examples.Utils; public static class Tools diff --git a/MaIN.Core.IntegrationTests/ChatTests.cs b/MaIN.Core.IntegrationTests/ChatTests.cs index 89952c05..f0b78969 100644 --- a/MaIN.Core.IntegrationTests/ChatTests.cs +++ b/MaIN.Core.IntegrationTests/ChatTests.cs @@ -64,11 +64,12 @@ public async Task Should_AnswerGameFromImage_ChatWithVision() var result = await AIHub.Chat() .WithModel("llama3.2:3b") + .WithMessage("What is the title of game?") + .WithMemoryParams(new MemoryParams { AnswerTokens = 1000 }) - .WithMessage("What is the title of game?") .WithFiles(images) .CompleteAsync(); diff --git a/MaIN.Core.IntegrationTests/IntegrationTestBase.cs b/MaIN.Core.IntegrationTests/IntegrationTestBase.cs index 19e76807..bfbcaed9 100644 --- a/MaIN.Core.IntegrationTests/IntegrationTestBase.cs +++ b/MaIN.Core.IntegrationTests/IntegrationTestBase.cs @@ -1,9 +1,5 @@ using System.Net.Sockets; -using MaIN.Services; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/MaIN.Core.UnitTests/AgentContextTests.cs b/src/MaIN.Core.UnitTests/AgentContextTests.cs index a31712ce..18694336 100644 --- a/src/MaIN.Core.UnitTests/AgentContextTests.cs +++ b/src/MaIN.Core.UnitTests/AgentContextTests.cs @@ -2,7 +2,6 @@ using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; using MaIN.Domain.Entities.Agents.Knowledge; -using MaIN.Services.Dtos; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using Moq; diff --git a/src/MaIN.Core.UnitTests/ChatContextTests.cs b/src/MaIN.Core.UnitTests/ChatContextTests.cs index fc91b895..6da951c6 100644 --- a/src/MaIN.Core.UnitTests/ChatContextTests.cs +++ b/src/MaIN.Core.UnitTests/ChatContextTests.cs @@ -1,6 +1,5 @@ using MaIN.Core.Hub.Contexts; using MaIN.Domain.Entities; -using MaIN.Services.Dtos; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using Moq; diff --git a/src/MaIN.Core/Hub/AiHub.cs b/src/MaIN.Core/Hub/AiHub.cs index 14a4000f..888946f3 100644 --- a/src/MaIN.Core/Hub/AiHub.cs +++ b/src/MaIN.Core/Hub/AiHub.cs @@ -26,7 +26,7 @@ internal static void Initialize(IAIHubServices services, _services ?? throw new AIHubNotInitializedException(); - public static ModelContext Model() => new ModelContext(_settings, _httpClientFactory); + public static ModelContext Model() => new(_settings, _httpClientFactory); public static ChatContext Chat() => new(Services.ChatService); public static AgentContext Agent() => new(Services.AgentService); public static FlowContext Flow() => new(Services.FlowService, Services.AgentService); diff --git a/src/MaIN.Core/Hub/Contexts/AgentContext.cs b/src/MaIN.Core/Hub/Contexts/AgentContext.cs index e032a559..aa313622 100644 --- a/src/MaIN.Core/Hub/Contexts/AgentContext.cs +++ b/src/MaIN.Core/Hub/Contexts/AgentContext.cs @@ -1,3 +1,4 @@ +using MaIN.Core.Hub.Contexts.Interfaces.AgentContext; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; @@ -8,13 +9,12 @@ using MaIN.Core.Hub.Utils; using MaIN.Domain.Entities.Agents.Knowledge; using MaIN.Domain.Entities.Tools; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Agents; using MaIN.Services.Constants; namespace MaIN.Core.Hub.Contexts; -public sealed class AgentContext +public sealed class AgentContext : IAgentBuilderEntryPoint, IAgentConfigurationBuilder, IAgentContextExecutor { private readonly IAgentService _agentService; private InferenceParams? _inferenceParams; @@ -50,31 +50,69 @@ internal AgentContext(IAgentService agentService, Agent existingAgent) _agent = existingAgent; } - public AgentContext WithId(string id) + // --- IAgentActions --- + public string GetAgentId() => _agent.Id; + public Agent GetAgent() => _agent; + public Knowledge? GetKnowledge() => _knowledge; + public async Task GetChat() => await _agentService.GetChatByAgent(_agent.Id); + public async Task RestartChat() => await _agentService.Restart(_agent.Id); + public async Task> GetAllAgents() => await _agentService.GetAgents(); + public async Task GetAgentById(string id) => await _agentService.GetAgentById(id); + public async Task Delete() => await _agentService.DeleteAgent(_agent.Id); + public async Task Exists() => await _agentService.AgentExists(_agent.Id); + + + public IAgentConfigurationBuilder WithModel(string model) { - _agent.Id = id; + _agent.Model = model; return this; } + + public IAgentConfigurationBuilder WithCustomModel(string model, string path, string? mmProject = null) + { + KnownModels.AddModel(model, path, mmProject); + _agent.Model = model; + return this; + } + + public async Task FromExisting(string agentId) + { + var existingAgent = await _agentService.GetAgentById(agentId); + if (existingAgent == null) + { + throw new AgentNotFoundException(agentId); + } + + var context = new AgentContext(_agentService, existingAgent); + context.LoadExistingKnowledgeIfExists(); + return context; + } - public string GetAgentId() => _agent.Id; - - public Agent GetAgent() => _agent; - - public Knowledge? GetKnowledge() => _knowledge; + public IAgentConfigurationBuilder WithInitialPrompt(string prompt) + { + _agent.Context.Instruction = prompt; + return this; + } - public AgentContext WithOrder(int order) + public IAgentConfigurationBuilder WithId(string id) + { + _agent.Id = id; + return this; + } + + public IAgentConfigurationBuilder WithOrder(int order) { _agent.Order = order; return this; } - public AgentContext DisableCache() + public IAgentConfigurationBuilder DisableCache() { _disableCache = true; return this; } - public AgentContext WithSource(IAgentSource source, AgentSourceType type) + public IAgentConfigurationBuilder WithSource(IAgentSource source, AgentSourceType type) { _agent.Context.Source = new AgentSource() { @@ -84,25 +122,19 @@ public AgentContext WithSource(IAgentSource source, AgentSourceType type) return this; } - public AgentContext WithName(string name) + public IAgentConfigurationBuilder WithName(string name) { _agent.Name = name; return this; } - public AgentContext WithBackend(BackendType backendType) + public IAgentConfigurationBuilder WithBackend(BackendType backendType) { _agent.Backend = backendType; return this; } - public AgentContext WithModel(string model) - { - _agent.Model = model; - return this; - } - - public AgentContext WithMcpConfig(Mcp mcpConfig) + public IAgentConfigurationBuilder WithMcpConfig(Mcp mcpConfig) { if (_agent.Backend != null) { @@ -112,57 +144,44 @@ public AgentContext WithMcpConfig(Mcp mcpConfig) return this; } - public AgentContext WithInferenceParams(InferenceParams inferenceParams) + public IAgentConfigurationBuilder WithInferenceParams(InferenceParams inferenceParams) { _inferenceParams = inferenceParams; return this; } - public AgentContext WithMemoryParams(MemoryParams memoryParams) + public IAgentConfigurationBuilder WithMemoryParams(MemoryParams memoryParams) { _memoryParams = memoryParams; return this; } - public AgentContext WithCustomModel(string model, string path, string? mmProject = null) - { - KnownModels.AddModel(model, path, mmProject); - _agent.Model = model; - return this; - } - - public AgentContext WithInitialPrompt(string prompt) - { - _agent.Context.Instruction = prompt; - return this; - } - - public AgentContext WithSteps(List? steps) + public IAgentConfigurationBuilder WithSteps(List? steps) { _agent.Context.Steps = steps; return this; } - public AgentContext WithKnowledge(Func knowledgeConfig) + public IAgentConfigurationBuilder WithKnowledge(Func knowledgeConfig) { var builder = KnowledgeBuilder.Instance.ForAgent(_agent); _knowledge = knowledgeConfig(builder).Build(); return this; } - public AgentContext WithKnowledge(KnowledgeBuilder knowledge) + public IAgentConfigurationBuilder WithKnowledge(KnowledgeBuilder knowledge) { _knowledge = knowledge.ForAgent(_agent).Build(); return this; } - public AgentContext WithKnowledge(Knowledge knowledge) + public IAgentConfigurationBuilder WithKnowledge(Knowledge knowledge) { _knowledge = knowledge; return this; } - public AgentContext WithInMemoryKnowledge(Func knowledgeConfig) + public IAgentConfigurationBuilder WithInMemoryKnowledge(Func knowledgeConfig) { var builder = KnowledgeBuilder.Instance .ForAgent(_agent) @@ -171,7 +190,7 @@ public AgentContext WithInMemoryKnowledge(Func(); _agent.Behaviours[name] = instruction; @@ -179,19 +198,19 @@ public AgentContext WithBehaviour(string name, string instruction) return this; } - public async Task CreateAsync(bool flow = false, bool interactiveResponse = false) + public async Task CreateAsync(bool flow = false, bool interactiveResponse = false) { await _agentService.CreateAgent(_agent, flow, interactiveResponse, _inferenceParams, _memoryParams, _disableCache); return this; } - public AgentContext Create(bool flow = false, bool interactiveResponse = false) + public IAgentContextExecutor Create(bool flow = false, bool interactiveResponse = false) { _ = _agentService.CreateAgent(_agent, flow, interactiveResponse, _inferenceParams, _memoryParams, _disableCache).Result; return this; } - public AgentContext WithTools(ToolsConfiguration toolsConfiguration) + public IAgentConfigurationBuilder WithTools(ToolsConfiguration toolsConfiguration) { _agent.ToolsConfiguration = toolsConfiguration; return this; @@ -309,36 +328,6 @@ public async Task ProcessAsync( }; } - public async Task GetChat() - { - return await _agentService.GetChatByAgent(_agent.Id); - } - - public async Task RestartChat() - { - return await _agentService.Restart(_agent.Id); - } - - public async Task> GetAllAgents() - { - return await _agentService.GetAgents(); - } - - public async Task GetAgentById(string id) - { - return await _agentService.GetAgentById(id); - } - - public async Task Delete() - { - await _agentService.DeleteAgent(_agent.Id); - } - - public async Task Exists() - { - return await _agentService.AgentExists(_agent.Id); - } - public static async Task FromExisting(IAgentService agentService, string agentId) { var existingAgent = await agentService.GetAgentById(agentId); diff --git a/src/MaIN.Core/Hub/Contexts/ChatContext.cs b/src/MaIN.Core/Hub/Contexts/ChatContext.cs index 1b6becd9..314ff851 100644 --- a/src/MaIN.Core/Hub/Contexts/ChatContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ChatContext.cs @@ -1,7 +1,7 @@ +using MaIN.Core.Hub.Contexts.Interfaces.ChatContext; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Domain.Entities.Tools; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Chats; using MaIN.Domain.Models; using MaIN.Services; @@ -12,7 +12,7 @@ namespace MaIN.Core.Hub.Contexts; -public sealed class ChatContext +public sealed class ChatContext : IChatBuilderEntryPoint, IChatMessageBuilder, IChatCompletionBuilder { private readonly IChatService _chatService; private bool _preProcess; @@ -38,72 +38,78 @@ internal ChatContext(IChatService chatService, Chat existingChat) _chat = existingChat; } - public ChatContext WithModel(string model) + + public IChatMessageBuilder WithModel(string model) { _chat.Model = model; return this; } - - public ChatContext WithInferenceParams(InferenceParams inferenceParams) + + public IChatMessageBuilder WithCustomModel(string model, string path, string? mmProject = null) { - _chat.InterferenceParams = inferenceParams; + KnownModels.AddModel(model, path, mmProject); + _chat.Model = model; return this; } - public ChatContext WithTools(ToolsConfiguration toolsConfiguration) + public IChatMessageBuilder EnableVisual() { - _chat.ToolsConfiguration = toolsConfiguration; + _chat.Visual = true; return this; } - public ChatContext WithMemoryParams(MemoryParams memoryParams) + public IChatCompletionBuilder WithInferenceParams(InferenceParams inferenceParams) { - _chat.MemoryParams = memoryParams; + _chat.InterferenceParams = inferenceParams; return this; } - public ChatContext WithCustomModel(string model, string path, string? mmProject = null) + public IChatCompletionBuilder WithTools(ToolsConfiguration toolsConfiguration) { - KnownModels.AddModel(model, path, mmProject); - _chat.Model = model; + _chat.ToolsConfiguration = toolsConfiguration; return this; } - public ChatContext Speak(TextToSpeechParams textToSpeechParams) + public IChatCompletionBuilder WithMemoryParams(MemoryParams memoryParams) { - _chat.Visual = false; - _chat.TextToSpeechParams = textToSpeechParams; - + _chat.MemoryParams = memoryParams; return this; } - public ChatContext WithBackend(BackendType backendType) + public IChatCompletionBuilder Speak(TextToSpeechParams speechParams) { - _chat.Backend = backendType; + _chat.Visual = false; + _chat.TextToSpeechParams = speechParams; return this; } - public ChatContext WithMessages(IEnumerable messages) + public IChatCompletionBuilder WithBackend(BackendType backendType) { - _chat.Messages.AddRange(messages); + _chat.Backend = backendType; return this; } - - public ChatContext WithMessage(string content) + + public IChatCompletionBuilder WithSystemPrompt(string systemPrompt) { var message = new Message { - Role = "User", - Content = content, - Type = MessageType.LocalLLM, + Role = "System", + Content = systemPrompt, + Type = MessageType.NotSet, Time = DateTime.Now }; - - _chat.Messages.Add(message); + + _chat.Messages.Insert(0, message); return this; } - - public ChatContext WithMessage(string content, byte[] image) + + public IChatCompletionBuilder WithMessage(string content) + { + _chat.Messages.Add(new Message { Role = "User", Content = content, Type = MessageType.LocalLLM, Time = DateTime.Now }); + return this; + } + + public IChatCompletionBuilder WithMessage(string content, byte[] image) { var message = new Message { @@ -113,77 +119,53 @@ public ChatContext WithMessage(string content, byte[] image) Time = DateTime.Now, Image = image }; - + _chat.Messages.Add(message); return this; } - public ChatContext WithSystemPrompt(string systemPrompt) + public IChatCompletionBuilder WithMessages(IEnumerable messages) { - var message = new Message - { - Role = "System", - Content = systemPrompt, - Type = MessageType.NotSet, - Time = DateTime.Now - }; - - // Insert system message at the beginning - _chat.Messages.Insert(0, message); + _chat.Messages.AddRange(messages); return this; } - public ChatContext WithFiles(List fileStreams, bool preProcess = false) + public IChatCompletionBuilder WithFiles(List file, bool preProcess = false) { - var files = fileStreams.Select(p => new FileInfo() - { - Name = Path.GetFileName(p.Name), - Path = null, - Extension = Path.GetExtension(p.Name), - StreamContent = p - }).ToList(); - + _files = file.Select(f => new FileInfo { Name = Path.GetFileName(f.Name), StreamContent = f, Extension = Path.GetExtension(f.Name) }) + .ToList(); _preProcess = preProcess; - _files = files; return this; } - public ChatContext WithFiles(List files, bool preProcess = false) + public IChatCompletionBuilder WithFiles(List file, bool preProcess = false) { - _files = files; + _files = file; _preProcess = preProcess; return this; } - - public ChatContext WithFiles(List filePaths, bool preProcess = false) - { - var files = filePaths.Select(p => new FileInfo() - { - Name = Path.GetFileName(p), - Path = p, - Extension = Path.GetExtension(p) - }).ToList(); - _preProcess = preProcess; - _files = files; - return this; - } - - public ChatContext EnableVisual() + public IChatCompletionBuilder WithFiles(List file, bool preProcess = false) { - _chat.Visual = true; - _chat.TextToSpeechParams = null; + _files = file + .Select(path => + new FileInfo + { + Name = Path.GetFileName(path), + Path = path, + Extension = Path.GetExtension(path) + }) + .ToList(); + _preProcess = preProcess; return this; } - public ChatContext DisableCache() + public IChatCompletionBuilder DisableCache() { _chat.Properties.AddProperty(ServiceConstants.Properties.DisableCacheProperty); return this; } - public string GetChatId() => _chat.Id; - public async Task CompleteAsync( bool translate = false, bool interactive = false, @@ -208,15 +190,40 @@ public async Task CompleteAsync( _files = []; return result; } + + public async Task FromExisting(string chatId) + { + var existing = await _chatService.GetById(chatId); + return existing == null + ? throw new ChatNotFoundException(chatId) + : new ChatContext(_chatService, existing); + } + + private async Task ChatExists(string id) + { + try + { + await _chatService.GetById(id); + return true; + } + catch + { + return false; + } + } + IChatMessageBuilder IChatMessageBuilder.EnableVisual() => EnableVisual(); + + public string GetChatId() => _chat.Id; + public async Task GetCurrentChat() { if (_chat.Id == null) { throw new ChatNotInitializedException(); } - + return await _chatService.GetById(_chat.Id); } @@ -224,41 +231,17 @@ public async Task> GetAllChats() { return await _chatService.GetAll(); } - + public async Task DeleteChat() { if (_chat.Id == null) { throw new ChatNotInitializedException(); } - - await _chatService.Delete(_chat.Id); - } - - private async Task ChatExists(string id) - { - try - { - await _chatService.GetById(id); - return true; - } - catch - { - return false; - } - } - // Static methods to create builder from existing chat - public async Task FromExisting(string chatId) - { - var existingChat = await _chatService.GetById(chatId); - if (existingChat == null) - { - throw new ChatNotFoundException(chatId); - } - return new ChatContext(_chatService, existingChat); + await _chatService.Delete(_chat.Id); } - + public List GetChatHistory() { return _chat.Messages.Select(x => new MessageShort() diff --git a/src/MaIN.Core/Hub/Contexts/FlowContext.cs b/src/MaIN.Core/Hub/Contexts/FlowContext.cs index 4a11e96d..7211d55d 100644 --- a/src/MaIN.Core/Hub/Contexts/FlowContext.cs +++ b/src/MaIN.Core/Hub/Contexts/FlowContext.cs @@ -4,10 +4,7 @@ using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; using MaIN.Domain.Entities.Agents.AgentSource; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Flows; -using MaIN.Services.Dtos; -using MaIN.Services.Mappers; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentActions.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentActions.cs new file mode 100644 index 00000000..204477fc --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentActions.cs @@ -0,0 +1,62 @@ +using MaIN.Domain.Entities; +using MaIN.Domain.Entities.Agents; +using MaIN.Domain.Entities.Agents.Knowledge; + +namespace MaIN.Core.Hub.Contexts.Interfaces.AgentContext; + +public interface IAgentActions +{ + /// + /// Gets the unique identifier (GUID) of the current Agent. + /// + /// The Agent's ID as a string. + string GetAgentId(); + + /// + /// Retrieves the full Agent entity with its current configuration and state. + /// + /// An object. + Agent GetAgent(); + + /// + /// Gets the knowledge base (Knowledge) assigned to this Agent. + /// + /// A object or null if not configured. + Knowledge? GetKnowledge(); + + /// + /// Retrieves the current chat session associated with this Agent. + /// + /// A task representing the asynchronous operation, containing the . + Task GetChat(); + + /// + /// Clears the conversation history and restarts the chat session for this Agent. + /// + /// A task representing the asynchronous operation, containing a new . + Task RestartChat(); + + /// + /// Lists all agents available in the system. + /// + /// A task containing a list of objects. + Task> GetAllAgents(); + + /// + /// Retrieves a specific agent by its unique identifier. + /// + /// The unique identifier of the Agent. + /// The object or null if not found. + Task GetAgentById(string id); + + /// + /// Permanently deletes the current Agent and all associated data. + /// + Task Delete(); + + /// + /// Checks if an agent with the current identifier exists in the system. + /// + /// True if the agent exists, otherwise false. + Task Exists(); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentBuilderEntryPoint.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentBuilderEntryPoint.cs new file mode 100644 index 00000000..19990bb2 --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentBuilderEntryPoint.cs @@ -0,0 +1,25 @@ +namespace MaIN.Core.Hub.Contexts.Interfaces.AgentContext; + +public interface IAgentBuilderEntryPoint : IAgentActions +{ + /// + /// Sets the LLM model to be used by the Agent. + /// + /// The name or identifier of the model (e.g., "llama3.2"). + IAgentConfigurationBuilder WithModel(string model); + + /// + /// Configures a custom model from a specific local path. + /// + /// A custom name for the model. + /// The file system path to the model files. + /// Optional multi-modal project identifier. + IAgentConfigurationBuilder WithCustomModel(string model, string path, string? mmProject = null); + + /// + /// Loads an existing Agent from the database. + /// + /// The unique identifier of the Agent to load. + /// An execution interface ready for processing messages. + Task FromExisting(string agentId); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentConfigurationBuilder.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentConfigurationBuilder.cs new file mode 100644 index 00000000..bba02aaa --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentConfigurationBuilder.cs @@ -0,0 +1,128 @@ +using MaIN.Core.Hub.Utils; +using MaIN.Domain.Configuration; +using MaIN.Domain.Entities; +using MaIN.Domain.Entities.Agents.AgentSource; +using MaIN.Domain.Entities.Agents.Knowledge; +using MaIN.Domain.Entities.Tools; + +namespace MaIN.Core.Hub.Contexts.Interfaces.AgentContext; + +public interface IAgentConfigurationBuilder : IAgentActions +{ + /// + /// Sets the system-level instruction or persona for the Agent. This prompt defines how the Agent should behave and respond. + /// + /// The text content of the system instructions or Agent persona. + IAgentConfigurationBuilder WithInitialPrompt(string prompt); + + /// + /// Sets a custom unique identifier for the Agent. + /// + /// The new Agent ID. + IAgentConfigurationBuilder WithId(string id); + + /// + /// Sets the Agent's execution order (relevant in multi-agent flows). + /// + /// The sequence number. + IAgentConfigurationBuilder WithOrder(int order); + + /// + /// Disables the caching mechanism for this Agent's requests. + /// + IAgentConfigurationBuilder DisableCache(); + + /// + /// Defines the data source from which the Agent derives its identity or knowledge. + /// + /// The implementation of the agent source. + /// The type of source (e.g., PDF, Web). + IAgentConfigurationBuilder WithSource(IAgentSource source, AgentSourceType type); + + /// + /// Sets a friendly display name for the Agent. + /// + /// The name of the Agent. + IAgentConfigurationBuilder WithName(string name); + + /// + /// Selects a specific processing engine (Backend) for LLM requests. + /// + /// The backend type. + IAgentConfigurationBuilder WithBackend(BackendType backendType); + + /// + /// Configures integration with the Model Context Protocol (MCP). + /// + /// The MCP configuration object. + IAgentConfigurationBuilder WithMcpConfig(Mcp mcpConfig); + + /// + /// Sets inference parameters such as temperature, top-p, or max tokens. + /// + /// An object containing LLM technical settings. + IAgentConfigurationBuilder WithInferenceParams(InferenceParams inferenceParams); + + /// + /// Configures memory and context window settings for the Agent. + /// + /// Memory management settings. + IAgentConfigurationBuilder WithMemoryParams(MemoryParams memoryParams); + + /// + /// Defines a sequence of steps (pipeline) that the Agent must execute for each request. + /// + /// A list of step names. + IAgentConfigurationBuilder WithSteps(List? steps); + + /// + /// Assigns tools (functions) that the Agent can autonomously invoke. + /// + /// The configuration for available tools. + IAgentConfigurationBuilder WithTools(ToolsConfiguration toolsConfiguration); + + /// + /// Configures the Agent's knowledge base using a configuration delegate. + /// + /// A function to configure the knowledge builder. + IAgentConfigurationBuilder WithKnowledge(Func knowledgeConfig); + + /// + /// Initializes the knowledge base using a pre-configured builder. + /// + /// The knowledge builder instance. + IAgentConfigurationBuilder WithKnowledge(KnowledgeBuilder knowledge); + + /// + /// Assigns a pre-built knowledge base object to the Agent. + /// + /// The knowledge instance. + IAgentConfigurationBuilder WithKnowledge(Knowledge knowledge); + + /// + /// Creates a volatile knowledge base stored only in memory (not persisted to disk). + /// + /// A function to configure the in-memory knowledge builder. + IAgentConfigurationBuilder WithInMemoryKnowledge(Func knowledgeConfig); + + /// + /// Defines a specific personality or task (Behavior) under a unique name. + /// + /// The name of the behavior. + /// The system instructions for this behavior. + IAgentConfigurationBuilder WithBehaviour(string name, string instruction); + + /// + /// Finalizes the build process and creates the Agent in the system synchronously. + /// + /// Indicates if the Agent is part of a larger flow. + /// Specifies if responses should be streamed. + IAgentContextExecutor Create(bool flow = false, bool interactiveResponse = false); + + /// + /// Finalizes the build process and creates the Agent in the system asynchronously. + /// + /// Indicates if the Agent is part of a larger flow. + /// Specifies if responses should be interactive. + Task CreateAsync(bool flow = false, bool interactiveResponse = false); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentContextExecutor.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentContextExecutor.cs new file mode 100644 index 00000000..bb85cfcc --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/AgentContext/IAgentContextExecutor.cs @@ -0,0 +1,47 @@ +using MaIN.Domain.Entities; +using MaIN.Domain.Entities.Tools; +using MaIN.Domain.Models; +using MaIN.Services.Services.Models; + +namespace MaIN.Core.Hub.Contexts.Interfaces.AgentContext; + +public interface IAgentContextExecutor : IAgentActions +{ + /// + /// Processes a request based on a full chat object including history. + /// + /// The chat object with history. + /// Indicates if the result should be translated. + /// A containing the model's response. + Task ProcessAsync(Chat chat, bool translate = false); + + /// + /// Processes a simple text message from the user. + /// + /// The text content of the message. + /// Indicates if the result should be translated. + /// Optional callback for receiving streaming tokens. + /// Optional callback for tool invocation events. + /// A containing the model's response. + Task ProcessAsync(string message, bool translate = false, Func? tokenCallback = null, Func? toolCallback = null); + + /// + /// Processes a single message object (may include images or files). + /// + /// The object. + /// Indicates if the result should be translated. + /// Callback for streaming tokens. + /// Callback for tool invocations. + /// A containing the model's response. + Task ProcessAsync(Message message, bool translate = false, Func? tokenCallback = null, Func? toolCallback = null); + + /// + /// Processes a collection of messages, updating the Agent's chat state. + /// + /// A list of messages to process. + /// Indicates if the result should be translated. + /// Callback for streaming tokens. + /// Callback for tool invocations. + /// A containing the model's response. + Task ProcessAsync(IEnumerable messages, bool translate = false, Func? tokenCallback = null, Func? toolCallback = null); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatActions.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatActions.cs new file mode 100644 index 00000000..18b1d811 --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatActions.cs @@ -0,0 +1,31 @@ +using MaIN.Domain.Entities; + +namespace MaIN.Core.Hub.Contexts.Interfaces.ChatContext; + +public interface IChatActions +{ + /// + /// Gets the unique identifier (GUID) of the current chat session. + /// + string GetChatId(); + + /// + /// Retrieves the full chat object with all its messages and properties. + /// + Task GetCurrentChat(); + + /// + /// Lists all chat sessions stored in the system. + /// + Task> GetAllChats(); + + /// + /// Permanently deletes the current chat session and its history. + /// + Task DeleteChat(); + + /// + /// Provides a lightweight summary of the chat history (Role, Content, Time). + /// + List GetChatHistory(); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatBuilderEntryPoint.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatBuilderEntryPoint.cs new file mode 100644 index 00000000..3cb31a0d --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatBuilderEntryPoint.cs @@ -0,0 +1,30 @@ +namespace MaIN.Core.Hub.Contexts.Interfaces.ChatContext; + +public interface IChatBuilderEntryPoint : IChatActions +{ + /// + /// Sets the standard model to be used for the chat session. + /// + /// The name or identifier of the LLM model. + IChatMessageBuilder WithModel(string model); + + /// + /// Configures a custom model with a specific path and project context. + /// + /// The name of the custom model. + /// The path to the model files. + /// Optional multi-modal project identifier. + + IChatMessageBuilder WithCustomModel(string model, string path, string? mmProject = null); + /// + /// Enables visual/image generation mode. Use this method now if you do not plan to explicitly define the model. + /// Otherwise, you will be able to use this method after defining the model. + /// + IChatMessageBuilder EnableVisual(); + + /// + /// Loads an existing chat session from the database using its unique identifier. + /// + /// The GUID of the existing chat. + Task FromExisting(string chatId); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatCompletionBuilder.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatCompletionBuilder.cs new file mode 100644 index 00000000..ad45de02 --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatCompletionBuilder.cs @@ -0,0 +1,83 @@ +using MaIN.Domain.Configuration; +using MaIN.Domain.Entities; +using MaIN.Domain.Entities.Tools; +using MaIN.Domain.Models; +using MaIN.Services.Services.Models; +using FileInfo = MaIN.Domain.Entities.FileInfo; + +namespace MaIN.Core.Hub.Contexts.Interfaces.ChatContext; + +public interface IChatCompletionBuilder : IChatActions +{ + /// + /// Configures low-level LLM parameters like temperature, context size, max tokens etc. + /// + /// An object containing detailed inference settings for the LLM. + IChatCompletionBuilder WithInferenceParams(InferenceParams inferenceParams); + + /// + /// Attaches external tools/functions that the model can invoke during the conversation. + /// + /// Configuration defining available tools and their execution modes. + IChatCompletionBuilder WithTools(ToolsConfiguration toolsConfiguration); + + /// + /// Defines memory parameters for the chat. + /// + /// Configuration for how the chat context and memory should be handled. + IChatCompletionBuilder WithMemoryParams(MemoryParams memoryParams); + + /// + /// Configures the session to use Text-to-Speech for the model's responses. + /// + /// Parameters for the voice synthesis. + IChatCompletionBuilder Speak(TextToSpeechParams speechParams); + + /// + /// Sets the specific execution backend for the inference (e.g., Local, Cloud). + /// + /// The type of backend to be used for processing the request. + IChatCompletionBuilder WithBackend(BackendType backendType); + + /// + /// Sets a system-level prompt to guide the model's behavior, persona, and constraints. + /// + /// The text content of the system instructions. + IChatCompletionBuilder WithSystemPrompt(string systemPrompt); + + /// + /// Attaches a list of files provided as to the message context. + /// + /// A list of open file streams to be uploaded or analyzed. + /// If true, the files will be pre-processed (e.g., indexed) before sending. + IChatCompletionBuilder WithFiles(List file, bool preProcess = false); + + /// + /// Attaches a list of files provided as objects to the message context. + /// + /// A list of file metadata and content references. + /// If true, the files will be pre-processed (e.g., indexed) before sending. + IChatCompletionBuilder WithFiles(List file, bool preProcess = false); + + /// + /// Attaches a list of files from provided local system paths to the message context. + /// + /// A list of absolute or relative paths to the files. + /// If true, the files will be pre-processed (e.g., indexed) before sending. + IChatCompletionBuilder WithFiles(List file, bool preProcess = false); + + /// + /// Disables the internal caching mechanism for the upcoming request. + /// + IChatCompletionBuilder DisableCache(); + + /// + /// Sends the configured chat context to the service for completion. + /// + /// Indicates whether the response should be automatically translated. + /// If true, the response will be processed in streaming mode. + /// An optional callback invoked whenever a new token or update is received during streaming. + /// A task representing the asynchronous operation, containing the . + Task CompleteAsync(bool translate = false, bool interactive = false, Func? changeOfValue = null); + +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs new file mode 100644 index 00000000..3dd810c2 --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs @@ -0,0 +1,30 @@ +using MaIN.Domain.Entities; + +namespace MaIN.Core.Hub.Contexts.Interfaces.ChatContext; + +public interface IChatMessageBuilder : IChatActions +{ + /// + /// Enables visual/image generation mode. + /// + IChatMessageBuilder EnableVisual(); + + /// + /// Adds a single text message to the chat context. + /// + /// The text content of the message. + IChatCompletionBuilder WithMessage(string content); + + /// + /// Adds a message containing both text and image data. + /// + /// The text description or prompt. + /// The byte array containing image data. + IChatCompletionBuilder WithMessage(string content, byte[] image); + + /// + /// Appends a collection of messages to the chat. + /// + /// An enumerable list of message objects. + IChatCompletionBuilder WithMessages(IEnumerable messages); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/McpContext.cs b/src/MaIN.Core/Hub/Contexts/McpContext.cs index 73eabd4c..356ff538 100644 --- a/src/MaIN.Core/Hub/Contexts/McpContext.cs +++ b/src/MaIN.Core/Hub/Contexts/McpContext.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.MPC; using MaIN.Services.Constants; using MaIN.Services.Services.Abstract; diff --git a/src/MaIN.Core/Hub/Contexts/ModelContext.cs b/src/MaIN.Core/Hub/Contexts/ModelContext.cs index 9e046375..944d45af 100644 --- a/src/MaIN.Core/Hub/Contexts/ModelContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ModelContext.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Net; using MaIN.Domain.Configuration; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; using MaIN.Services.Constants; diff --git a/src/MaIN.Domain/Entities/Agents/Knowledge/Knowledge.cs b/src/MaIN.Domain/Entities/Agents/Knowledge/Knowledge.cs index 80df043c..9ccb204f 100644 --- a/src/MaIN.Domain/Entities/Agents/Knowledge/Knowledge.cs +++ b/src/MaIN.Domain/Entities/Agents/Knowledge/Knowledge.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using MaIN.Domain.Configuration; namespace MaIN.Domain.Entities.Agents.Knowledge; diff --git a/src/MaIN.Domain/Models/SupportedModels.cs b/src/MaIN.Domain/Models/SupportedModels.cs index 2ffeb7a3..d2fd3875 100644 --- a/src/MaIN.Domain/Models/SupportedModels.cs +++ b/src/MaIN.Domain/Models/SupportedModels.cs @@ -1,4 +1,3 @@ -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Models; namespace MaIN.Domain.Models; diff --git a/src/MaIN.InferPage/Components/Layout/MainLayout.razor b/src/MaIN.InferPage/Components/Layout/MainLayout.razor index fdf39a6d..0481fa40 100644 --- a/src/MaIN.InferPage/Components/Layout/MainLayout.razor +++ b/src/MaIN.InferPage/Components/Layout/MainLayout.razor @@ -1,5 +1,4 @@ -@using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular -@inherits LayoutComponentBase +@inherits LayoutComponentBase
diff --git a/src/MaIN.InferPage/Components/Layout/NavBar.razor b/src/MaIN.InferPage/Components/Layout/NavBar.razor index 3bc5ea8e..5216c58e 100644 --- a/src/MaIN.InferPage/Components/Layout/NavBar.razor +++ b/src/MaIN.InferPage/Components/Layout/NavBar.razor @@ -1,5 +1,4 @@ @using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular -@using Size32 = Microsoft.FluentUI.AspNetCore.Components.Icons.Filled.Size32 @inject NavigationManager _navigationManager @rendermode @(new InteractiveServerRenderMode(prerender: false)) diff --git a/src/MaIN.InferPage/Components/Pages/Home.razor b/src/MaIN.InferPage/Components/Pages/Home.razor index 8d1bd4ab..0c9c2722 100644 --- a/src/MaIN.InferPage/Components/Pages/Home.razor +++ b/src/MaIN.InferPage/Components/Pages/Home.razor @@ -3,6 +3,7 @@ @inject IJSRuntime JS @using MaIN.Core.Hub @using MaIN.Core.Hub.Contexts +@using MaIN.Core.Hub.Contexts.Interfaces.ChatContext @using MaIN.Domain.Entities @using MaIN.Domain.Exceptions @using MaIN.Domain.Models @@ -190,7 +191,8 @@ private string? _incomingMessage = null; private string? _incomingReasoning = null; private readonly string? _displayName = Utils.Model; - private ChatContext? ctx; + private IChatMessageBuilder? ctxBuilder; + // private ChatContext? ctx; private Chat Chat { get; } = new() { Name = "MaIN Infer", Model = Utils.Model! }; private List Messages { get; set; } = new(); private ElementReference? _bottomElement; @@ -206,7 +208,7 @@ protected override Task OnInitializedAsync() { - ctx = Utils.Visual + ctxBuilder = Utils.Visual ? AIHub.Chat().EnableVisual() : Utils.Path != null ? AIHub.Chat().WithCustomModel(model: Utils.Model!, path: Utils.Path) @@ -256,7 +258,7 @@ try { - await ctx!.WithMessage(msg) + await ctxBuilder!.WithMessage(msg) .CompleteAsync(changeOfValue: async message => { if (message?.Type == TokenType.Reason) @@ -278,7 +280,7 @@ }); _isLoading = false; - var currentChat = (await ctx.GetCurrentChat()); + var currentChat = (await ctxBuilder.GetCurrentChat()); Chat.Messages.Add(currentChat.Messages.Last()); Messages = Chat.Messages.Select(x => new MessageExt() { diff --git a/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs index 38066c4d..855cca2e 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Flows; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs index d66ce6e1..8a94b3ff 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Agents; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs index 6f084afb..246c9eea 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Chats; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs index a6e2696b..73a2db71 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Flows; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs index 3ba8e416..62f7e805 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Agents; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs index 12c3e957..1748dc11 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Chats; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Services/Mappers/ChatMapper.cs b/src/MaIN.Services/Mappers/ChatMapper.cs index c35819ce..4ccff914 100644 --- a/src/MaIN.Services/Mappers/ChatMapper.cs +++ b/src/MaIN.Services/Mappers/ChatMapper.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using LLama.Batched; using MaIN.Domain.Entities; using MaIN.Domain.Models; diff --git a/src/MaIN.Services/Services/Abstract/IChatService.cs b/src/MaIN.Services/Services/Abstract/IChatService.cs index 1984306c..c5ffb5f6 100644 --- a/src/MaIN.Services/Services/Abstract/IChatService.cs +++ b/src/MaIN.Services/Services/Abstract/IChatService.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Entities; using MaIN.Domain.Models; -using MaIN.Services.Dtos; using MaIN.Services.Services.Models; namespace MaIN.Services.Services.Abstract; diff --git a/src/MaIN.Services/Services/Abstract/IImageGenService.cs b/src/MaIN.Services/Services/Abstract/IImageGenService.cs index ca60763f..fc268787 100644 --- a/src/MaIN.Services/Services/Abstract/IImageGenService.cs +++ b/src/MaIN.Services/Services/Abstract/IImageGenService.cs @@ -1,5 +1,4 @@ using MaIN.Domain.Entities; -using MaIN.Services.Dtos; using MaIN.Services.Services.Models; namespace MaIN.Services.Services.Abstract; diff --git a/src/MaIN.Services/Services/Abstract/ILLMService.cs b/src/MaIN.Services/Services/Abstract/ILLMService.cs index 1547c43c..2722162c 100644 --- a/src/MaIN.Services/Services/Abstract/ILLMService.cs +++ b/src/MaIN.Services/Services/Abstract/ILLMService.cs @@ -1,6 +1,4 @@ using MaIN.Domain.Entities; -using MaIN.Domain.Models; -using MaIN.Services.Dtos; using MaIN.Services.Services.LLMService; using MaIN.Services.Services.Models; diff --git a/src/MaIN.Services/Services/AgentFlowService.cs b/src/MaIN.Services/Services/AgentFlowService.cs index cab06b76..b6937111 100644 --- a/src/MaIN.Services/Services/AgentFlowService.cs +++ b/src/MaIN.Services/Services/AgentFlowService.cs @@ -1,7 +1,5 @@ using MaIN.Domain.Entities.Agents.AgentSource; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Agents; -using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Mappers; using MaIN.Services.Services.Abstract; diff --git a/src/MaIN.Services/Services/AgentService.cs b/src/MaIN.Services/Services/AgentService.cs index e20255ca..c43c1db6 100644 --- a/src/MaIN.Services/Services/AgentService.cs +++ b/src/MaIN.Services/Services/AgentService.cs @@ -5,7 +5,6 @@ using MaIN.Domain.Entities.Agents.Knowledge; using MaIN.Domain.Entities.Tools; using MaIN.Domain.Models; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Agents; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Constants; @@ -14,7 +13,6 @@ using MaIN.Services.Services.ImageGenServices; using MaIN.Services.Services.LLMService.Factory; using MaIN.Services.Services.Models.Commands; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; using MaIN.Services.Utils; using Microsoft.Extensions.Logging; diff --git a/src/MaIN.Services/Services/ChatService.cs b/src/MaIN.Services/Services/ChatService.cs index e1a1a21b..193d8808 100644 --- a/src/MaIN.Services/Services/ChatService.cs +++ b/src/MaIN.Services/Services/ChatService.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Chats; using MaIN.Domain.Models; using MaIN.Infrastructure.Repositories.Abstract; diff --git a/src/MaIN.Services/Services/ImageGenServices/ImageGenService.cs b/src/MaIN.Services/Services/ImageGenServices/ImageGenService.cs index d060a4f6..56e67055 100644 --- a/src/MaIN.Services/Services/ImageGenServices/ImageGenService.cs +++ b/src/MaIN.Services/Services/ImageGenServices/ImageGenService.cs @@ -1,7 +1,3 @@ -using System; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Services.Constants; diff --git a/src/MaIN.Services/Services/LLMService/AnthropicService.cs b/src/MaIN.Services/Services/LLMService/AnthropicService.cs index d7d54c6d..9685cd5b 100644 --- a/src/MaIN.Services/Services/LLMService/AnthropicService.cs +++ b/src/MaIN.Services/Services/LLMService/AnthropicService.cs @@ -13,7 +13,6 @@ using MaIN.Domain.Entities.Tools; using MaIN.Domain.Exceptions; using MaIN.Services.Services.LLMService.Utils; -using MaIN.Services.Services.LLMService; namespace MaIN.Services.Services.LLMService; diff --git a/src/MaIN.Services/Services/LLMService/LLMService.cs b/src/MaIN.Services/Services/LLMService/LLMService.cs index f28b6479..ceb539e9 100644 --- a/src/MaIN.Services/Services/LLMService/LLMService.cs +++ b/src/MaIN.Services/Services/LLMService/LLMService.cs @@ -7,7 +7,6 @@ using LLama.Sampling; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; using MaIN.Services.Constants; diff --git a/src/MaIN.Services/Services/LLMService/Memory/IMemoryFactory.cs b/src/MaIN.Services/Services/LLMService/Memory/IMemoryFactory.cs index 97aa040d..c411ae63 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/IMemoryFactory.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/IMemoryFactory.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using LLama; -using LLamaSharp.KernelMemory; using MaIN.Domain.Entities; using MaIN.Services.Services.LLMService.Memory.Embeddings; using Microsoft.KernelMemory; diff --git a/src/MaIN.Services/Services/LLMService/Memory/IMemoryService.cs b/src/MaIN.Services/Services/LLMService/Memory/IMemoryService.cs index 2521af32..84cfbba7 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/IMemoryService.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/IMemoryService.cs @@ -1,4 +1,3 @@ -using LLamaSharp.KernelMemory; using Microsoft.KernelMemory; using Microsoft.KernelMemory.AI; diff --git a/src/MaIN.Services/Services/LLMService/Memory/KernelMemoryLlamaExtensions.cs b/src/MaIN.Services/Services/LLMService/Memory/KernelMemoryLlamaExtensions.cs index 262a4f08..fe5f90b5 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/KernelMemoryLlamaExtensions.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/KernelMemoryLlamaExtensions.cs @@ -2,7 +2,6 @@ using LLama.Batched; using LLama.Common; using LLama.Sampling; -using LLamaSharp.KernelMemory; using MaIN.Domain.Entities; using MaIN.Services.Services.LLMService.Memory.Embeddings; using Microsoft.KernelMemory; diff --git a/src/MaIN.Services/Services/LLMService/Memory/MemoryFactory.cs b/src/MaIN.Services/Services/LLMService/Memory/MemoryFactory.cs index 7a027e05..c8af60b0 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/MemoryFactory.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/MemoryFactory.cs @@ -3,7 +3,6 @@ using LLama.Common; using LLamaSharp.KernelMemory; using MaIN.Domain.Entities; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; using MaIN.Services.Services.LLMService.Memory.Embeddings; diff --git a/src/MaIN.Services/Services/LLMService/Memory/MemoryService.cs b/src/MaIN.Services/Services/LLMService/Memory/MemoryService.cs index 2dda0dcc..7485557a 100644 --- a/src/MaIN.Services/Services/LLMService/Memory/MemoryService.cs +++ b/src/MaIN.Services/Services/LLMService/Memory/MemoryService.cs @@ -1,5 +1,4 @@ using LLama.Native; -using LLamaSharp.KernelMemory; using MaIN.Services.Services.LLMService.Memory.Embeddings; using MaIN.Services.Utils; using Microsoft.KernelMemory; diff --git a/src/MaIN.Services/Services/Models/ChatResult.cs b/src/MaIN.Services/Services/Models/ChatResult.cs index fb5ccf12..19bdc6f1 100644 --- a/src/MaIN.Services/Services/Models/ChatResult.cs +++ b/src/MaIN.Services/Services/Models/ChatResult.cs @@ -1,6 +1,4 @@ -using System.Text.Json.Serialization; using MaIN.Domain.Entities; -using MaIN.Services.Dtos; namespace MaIN.Services.Services.Models; diff --git a/src/MaIN.Services/Services/Models/Commands/AnswerCommand.cs b/src/MaIN.Services/Services/Models/Commands/AnswerCommand.cs index 40523e08..d6fc8ae0 100644 --- a/src/MaIN.Services/Services/Models/Commands/AnswerCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/AnswerCommand.cs @@ -4,7 +4,6 @@ using MaIN.Domain.Models; using MaIN.Services.Constants; using MaIN.Services.Services.Models.Commands.Base; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/Commands/FetchCommand.cs b/src/MaIN.Services/Services/Models/Commands/FetchCommand.cs index 3ba5b4c1..a565b881 100644 --- a/src/MaIN.Services/Services/Models/Commands/FetchCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/FetchCommand.cs @@ -1,7 +1,6 @@ using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; using MaIN.Services.Services.Models.Commands.Base; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/Commands/McpCommand.cs b/src/MaIN.Services/Services/Models/Commands/McpCommand.cs index 78578650..2b776ece 100644 --- a/src/MaIN.Services/Services/Models/Commands/McpCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/McpCommand.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Entities; using MaIN.Services.Services.Models.Commands.Base; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/Commands/RedirectCommand.cs b/src/MaIN.Services/Services/Models/Commands/RedirectCommand.cs index a8a4bfe3..35868a21 100644 --- a/src/MaIN.Services/Services/Models/Commands/RedirectCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/RedirectCommand.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Entities; using MaIN.Services.Services.Models.Commands.Base; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/Commands/StartCommand.cs b/src/MaIN.Services/Services/Models/Commands/StartCommand.cs index 5a424b55..dd37364e 100644 --- a/src/MaIN.Services/Services/Models/Commands/StartCommand.cs +++ b/src/MaIN.Services/Services/Models/Commands/StartCommand.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Entities; using MaIN.Services.Services.Models.Commands.Base; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Models.Commands; diff --git a/src/MaIN.Services/Services/Models/McpResult.cs b/src/MaIN.Services/Services/Models/McpResult.cs index 5eebc03d..3351b2b2 100644 --- a/src/MaIN.Services/Services/Models/McpResult.cs +++ b/src/MaIN.Services/Services/Models/McpResult.cs @@ -1,6 +1,4 @@ -using System.Text.Json.Serialization; using MaIN.Domain.Entities; -using MaIN.Services.Dtos; namespace MaIN.Services.Services.Models; diff --git a/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs b/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs index 32665182..04328029 100644 --- a/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/AnswerStepHandler.cs @@ -1,12 +1,9 @@ using System.Text.RegularExpressions; -using DocumentFormat.OpenXml.Wordprocessing; -using MaIN.Domain.Entities; using MaIN.Services.Constants; using MaIN.Domain.Exceptions; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps; diff --git a/src/MaIN.Services/Services/Steps/Commands/McpCommandHandler.cs b/src/MaIN.Services/Services/Steps/Commands/McpCommandHandler.cs index 21e0af2f..8c6b96be 100644 --- a/src/MaIN.Services/Services/Steps/Commands/McpCommandHandler.cs +++ b/src/MaIN.Services/Services/Steps/Commands/McpCommandHandler.cs @@ -1,11 +1,5 @@ -using MaIN.Domain.Configuration; using MaIN.Domain.Entities; -using MaIN.Services.Dtos; -using MaIN.Services.Mappers; using MaIN.Services.Services.Abstract; -using MaIN.Services.Services.LLMService; -using MaIN.Services.Services.LLMService.Factory; -using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; diff --git a/src/MaIN.Services/Services/Steps/Commands/RedirectCommandHandler.cs b/src/MaIN.Services/Services/Steps/Commands/RedirectCommandHandler.cs index c3a8473b..6d9f6086 100644 --- a/src/MaIN.Services/Services/Steps/Commands/RedirectCommandHandler.cs +++ b/src/MaIN.Services/Services/Steps/Commands/RedirectCommandHandler.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; -using MaIN.Services.Constants; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; diff --git a/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs b/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs index 6a3991ea..b82e48c7 100644 --- a/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs @@ -5,7 +5,6 @@ using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps; diff --git a/src/MaIN.Services/Services/Steps/McpStepHandler.cs b/src/MaIN.Services/Services/Steps/McpStepHandler.cs index 7078cb5d..2fad4126 100644 --- a/src/MaIN.Services/Services/Steps/McpStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/McpStepHandler.cs @@ -1,12 +1,9 @@ using System.Text.RegularExpressions; -using DocumentFormat.OpenXml.Wordprocessing; -using MaIN.Domain.Entities; using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.MPC; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; -using MaIN.Services.Services.Steps.Commands; using MaIN.Services.Services.Steps.Commands.Abstract; namespace MaIN.Services.Services.Steps; diff --git a/src/MaIN.Services/Services/Steps/RedirectStepHandler.cs b/src/MaIN.Services/Services/Steps/RedirectStepHandler.cs index 7e6c1469..b976d4d3 100644 --- a/src/MaIN.Services/Services/Steps/RedirectStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/RedirectStepHandler.cs @@ -1,10 +1,6 @@ using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; using MaIN.Services.Services.Models.Commands; -using MaIN.Services.Services.Steps.Commands; -using System; -using System.Linq; -using System.Threading.Tasks; using MaIN.Domain.Entities; using MaIN.Services.Services.Steps.Commands.Abstract; diff --git a/src/MaIN.Services/Services/TTSService/TextToSpeechService.cs b/src/MaIN.Services/Services/TTSService/TextToSpeechService.cs index e7315112..3bc93a38 100644 --- a/src/MaIN.Services/Services/TTSService/TextToSpeechService.cs +++ b/src/MaIN.Services/Services/TTSService/TextToSpeechService.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Configuration; using MaIN.Domain.Entities; -using MaIN.Domain.Exceptions; using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; using NAudio.Wave; From 53857234880e984aeafa79afc817ef8a3b1dab26 Mon Sep 17 00:00:00 2001 From: Szymon Gurak Date: Tue, 20 Jan 2026 23:49:32 +0100 Subject: [PATCH 9/9] [#19] Add XML Documentation Comments to the Flow, Mcp, Model Contexts --- src/MaIN.Core/Hub/Contexts/ChatContext.cs | 30 ++--- src/MaIN.Core/Hub/Contexts/FlowContext.cs | 19 +-- .../ChatContext/IChatBuilderEntryPoint.cs | 2 +- ...uilder.cs => IChatConfigurationBuilder.cs} | 23 ++-- .../ChatContext/IChatMessageBuilder.cs | 6 +- .../Interfaces/FlowContext/IFlowContext.cs | 118 ++++++++++++++++++ .../Interfaces/McpContext/IMcpContext.cs | 29 +++++ .../Interfaces/ModelContext/IModelContext.cs | 88 +++++++++++++ src/MaIN.Core/Hub/Contexts/McpContext.cs | 7 +- src/MaIN.Core/Hub/Contexts/ModelContext.cs | 24 ++-- .../Models/MissingModelNameException.cs | 10 ++ 11 files changed, 302 insertions(+), 54 deletions(-) rename src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/{IChatCompletionBuilder.cs => IChatConfigurationBuilder.cs} (78%) create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/FlowContext/IFlowContext.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/McpContext/IMcpContext.cs create mode 100644 src/MaIN.Core/Hub/Contexts/Interfaces/ModelContext/IModelContext.cs create mode 100644 src/MaIN.Domain/Exceptions/Models/MissingModelNameException.cs diff --git a/src/MaIN.Core/Hub/Contexts/ChatContext.cs b/src/MaIN.Core/Hub/Contexts/ChatContext.cs index 314ff851..8f58c83e 100644 --- a/src/MaIN.Core/Hub/Contexts/ChatContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ChatContext.cs @@ -12,7 +12,7 @@ namespace MaIN.Core.Hub.Contexts; -public sealed class ChatContext : IChatBuilderEntryPoint, IChatMessageBuilder, IChatCompletionBuilder +public sealed class ChatContext : IChatBuilderEntryPoint, IChatMessageBuilder, IChatConfigurationBuilder { private readonly IChatService _chatService; private bool _preProcess; @@ -58,38 +58,38 @@ public IChatMessageBuilder EnableVisual() return this; } - public IChatCompletionBuilder WithInferenceParams(InferenceParams inferenceParams) + public IChatConfigurationBuilder WithInferenceParams(InferenceParams inferenceParams) { _chat.InterferenceParams = inferenceParams; return this; } - public IChatCompletionBuilder WithTools(ToolsConfiguration toolsConfiguration) + public IChatConfigurationBuilder WithTools(ToolsConfiguration toolsConfiguration) { _chat.ToolsConfiguration = toolsConfiguration; return this; } - public IChatCompletionBuilder WithMemoryParams(MemoryParams memoryParams) + public IChatConfigurationBuilder WithMemoryParams(MemoryParams memoryParams) { _chat.MemoryParams = memoryParams; return this; } - public IChatCompletionBuilder Speak(TextToSpeechParams speechParams) + public IChatConfigurationBuilder Speak(TextToSpeechParams speechParams) { _chat.Visual = false; _chat.TextToSpeechParams = speechParams; return this; } - public IChatCompletionBuilder WithBackend(BackendType backendType) + public IChatConfigurationBuilder WithBackend(BackendType backendType) { _chat.Backend = backendType; return this; } - public IChatCompletionBuilder WithSystemPrompt(string systemPrompt) + public IChatConfigurationBuilder WithSystemPrompt(string systemPrompt) { var message = new Message { @@ -103,13 +103,13 @@ public IChatCompletionBuilder WithSystemPrompt(string systemPrompt) return this; } - public IChatCompletionBuilder WithMessage(string content) + public IChatConfigurationBuilder WithMessage(string content) { _chat.Messages.Add(new Message { Role = "User", Content = content, Type = MessageType.LocalLLM, Time = DateTime.Now }); return this; } - public IChatCompletionBuilder WithMessage(string content, byte[] image) + public IChatConfigurationBuilder WithMessage(string content, byte[] image) { var message = new Message { @@ -124,13 +124,13 @@ public IChatCompletionBuilder WithMessage(string content, byte[] image) return this; } - public IChatCompletionBuilder WithMessages(IEnumerable messages) + public IChatConfigurationBuilder WithMessages(IEnumerable messages) { _chat.Messages.AddRange(messages); return this; } - public IChatCompletionBuilder WithFiles(List file, bool preProcess = false) + public IChatConfigurationBuilder WithFiles(List file, bool preProcess = false) { _files = file.Select(f => new FileInfo { Name = Path.GetFileName(f.Name), StreamContent = f, Extension = Path.GetExtension(f.Name) }) .ToList(); @@ -138,14 +138,14 @@ public IChatCompletionBuilder WithFiles(List file, bool preProcess = return this; } - public IChatCompletionBuilder WithFiles(List file, bool preProcess = false) + public IChatConfigurationBuilder WithFiles(List file, bool preProcess = false) { _files = file; _preProcess = preProcess; return this; } - public IChatCompletionBuilder WithFiles(List file, bool preProcess = false) + public IChatConfigurationBuilder WithFiles(List file, bool preProcess = false) { _files = file .Select(path => @@ -160,7 +160,7 @@ public IChatCompletionBuilder WithFiles(List file, bool preProcess = fal return this; } - public IChatCompletionBuilder DisableCache() + public IChatConfigurationBuilder DisableCache() { _chat.Properties.AddProperty(ServiceConstants.Properties.DisableCacheProperty); return this; @@ -191,7 +191,7 @@ public async Task CompleteAsync( return result; } - public async Task FromExisting(string chatId) + public async Task FromExisting(string chatId) { var existing = await _chatService.GetById(chatId); return existing == null diff --git a/src/MaIN.Core/Hub/Contexts/FlowContext.cs b/src/MaIN.Core/Hub/Contexts/FlowContext.cs index 7211d55d..449176bd 100644 --- a/src/MaIN.Core/Hub/Contexts/FlowContext.cs +++ b/src/MaIN.Core/Hub/Contexts/FlowContext.cs @@ -1,5 +1,6 @@ using System.IO.Compression; using System.Text.Json; +using MaIN.Core.Hub.Contexts.Interfaces.FlowContext; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; @@ -10,7 +11,7 @@ namespace MaIN.Core.Hub.Contexts; -public sealed class FlowContext +public sealed class FlowContext : IFlowContext { private readonly IAgentFlowService _flowService; private readonly IAgentService _agentService; @@ -36,25 +37,25 @@ internal FlowContext(IAgentFlowService flowService, IAgentService agentService, _flow = existingFlow; } - public FlowContext WithId(string id) + public IFlowContext WithId(string id) { _flow.Id = id; return this; } - public FlowContext WithName(string name) + public IFlowContext WithName(string name) { _flow.Name = name; return this; } - public FlowContext WithDescription(string description) + public IFlowContext WithDescription(string description) { _flow.Description = description; return this; } - public FlowContext Save(string path) + public IFlowContext Save(string path) { Directory.CreateDirectory(Path.GetDirectoryName(path)!); @@ -84,7 +85,7 @@ public FlowContext Save(string path) return this; } - public FlowContext Load(string path) + public IFlowContext Load(string path) { var fileName = Path.GetFileNameWithoutExtension(path); string description = ""; @@ -133,7 +134,7 @@ public FlowContext Load(string path) return this; } - public FlowContext AddAgent(Agent agent) + public IFlowContext AddAgent(Agent agent) { _flow.Agents.Add(agent); return this; @@ -190,7 +191,7 @@ public async Task ProcessAsync(Message message, bool translate = fal }; } - public FlowContext AddAgents(IEnumerable agents) + public IFlowContext AddAgents(IEnumerable agents) { foreach (var agent in agents) { @@ -229,7 +230,7 @@ public async Task> GetAllFlows() } // Static factory methods - public async Task FromExisting(string flowId) + public async Task FromExisting(string flowId) { var existingFlow = await _flowService.GetFlowById(flowId); return existingFlow == null diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatBuilderEntryPoint.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatBuilderEntryPoint.cs index 3cb31a0d..2cbbf69d 100644 --- a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatBuilderEntryPoint.cs +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatBuilderEntryPoint.cs @@ -26,5 +26,5 @@ public interface IChatBuilderEntryPoint : IChatActions /// Loads an existing chat session from the database using its unique identifier. ///
/// The GUID of the existing chat. - Task FromExisting(string chatId); + Task FromExisting(string chatId); } \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatCompletionBuilder.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatConfigurationBuilder.cs similarity index 78% rename from src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatCompletionBuilder.cs rename to src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatConfigurationBuilder.cs index ad45de02..d12dfc60 100644 --- a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatCompletionBuilder.cs +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatConfigurationBuilder.cs @@ -7,69 +7,70 @@ namespace MaIN.Core.Hub.Contexts.Interfaces.ChatContext; -public interface IChatCompletionBuilder : IChatActions +public interface IChatConfigurationBuilder : IChatActions { /// /// Configures low-level LLM parameters like temperature, context size, max tokens etc. /// /// An object containing detailed inference settings for the LLM. - IChatCompletionBuilder WithInferenceParams(InferenceParams inferenceParams); + IChatConfigurationBuilder WithInferenceParams(InferenceParams inferenceParams); /// /// Attaches external tools/functions that the model can invoke during the conversation. /// /// Configuration defining available tools and their execution modes. - IChatCompletionBuilder WithTools(ToolsConfiguration toolsConfiguration); + IChatConfigurationBuilder WithTools(ToolsConfiguration toolsConfiguration); /// /// Defines memory parameters for the chat. /// /// Configuration for how the chat context and memory should be handled. - IChatCompletionBuilder WithMemoryParams(MemoryParams memoryParams); + IChatConfigurationBuilder WithMemoryParams(MemoryParams memoryParams); /// /// Configures the session to use Text-to-Speech for the model's responses. /// /// Parameters for the voice synthesis. - IChatCompletionBuilder Speak(TextToSpeechParams speechParams); + IChatConfigurationBuilder Speak(TextToSpeechParams speechParams); /// /// Sets the specific execution backend for the inference (e.g., Local, Cloud). /// /// The type of backend to be used for processing the request. - IChatCompletionBuilder WithBackend(BackendType backendType); + IChatConfigurationBuilder WithBackend(BackendType backendType); /// /// Sets a system-level prompt to guide the model's behavior, persona, and constraints. /// /// The text content of the system instructions. - IChatCompletionBuilder WithSystemPrompt(string systemPrompt); + IChatConfigurationBuilder WithSystemPrompt(string systemPrompt); /// /// Attaches a list of files provided as to the message context. /// /// A list of open file streams to be uploaded or analyzed. /// If true, the files will be pre-processed (e.g., indexed) before sending. - IChatCompletionBuilder WithFiles(List file, bool preProcess = false); + IChatConfigurationBuilder WithFiles(List file, bool preProcess = false); /// /// Attaches a list of files provided as objects to the message context. /// /// A list of file metadata and content references. /// If true, the files will be pre-processed (e.g., indexed) before sending. - IChatCompletionBuilder WithFiles(List file, bool preProcess = false); + IChatConfigurationBuilder WithFiles(List file, bool preProcess = false); /// /// Attaches a list of files from provided local system paths to the message context. /// /// A list of absolute or relative paths to the files. /// If true, the files will be pre-processed (e.g., indexed) before sending. - IChatCompletionBuilder WithFiles(List file, bool preProcess = false); + /// The builder instance implementing to enable method chaining. + IChatConfigurationBuilder WithFiles(List file, bool preProcess = false); /// /// Disables the internal caching mechanism for the upcoming request. /// - IChatCompletionBuilder DisableCache(); + IChatConfigurationBuilder DisableCache(); /// /// Sends the configured chat context to the service for completion. diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs index 3dd810c2..c160d8f3 100644 --- a/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs @@ -13,18 +13,18 @@ public interface IChatMessageBuilder : IChatActions /// Adds a single text message to the chat context. /// /// The text content of the message. - IChatCompletionBuilder WithMessage(string content); + IChatConfigurationBuilder WithMessage(string content); /// /// Adds a message containing both text and image data. /// /// The text description or prompt. /// The byte array containing image data. - IChatCompletionBuilder WithMessage(string content, byte[] image); + IChatConfigurationBuilder WithMessage(string content, byte[] image); /// /// Appends a collection of messages to the chat. /// /// An enumerable list of message objects. - IChatCompletionBuilder WithMessages(IEnumerable messages); + IChatConfigurationBuilder WithMessages(IEnumerable messages); } \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/FlowContext/IFlowContext.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/FlowContext/IFlowContext.cs new file mode 100644 index 00000000..af64aa51 --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/FlowContext/IFlowContext.cs @@ -0,0 +1,118 @@ +using MaIN.Domain.Entities; +using MaIN.Domain.Entities.Agents; +using MaIN.Domain.Entities.Agents.AgentSource; +using MaIN.Services.Services.Models; + +namespace MaIN.Core.Hub.Contexts.Interfaces.FlowContext; + +public interface IFlowContext +{ + /// + /// Assigns a unique identifier to the flow. + /// + /// The unique identifier for the flow. + /// The IFlowContext instance to enable method chaining. + IFlowContext WithId(string id); + + /// + /// Assigns a custom name to the flow. + /// + /// The custom name for the flow. + /// The IFlowContext instance to enable method chaining. + IFlowContext WithName(string name); + + /// + /// Sets a description for the flow. + /// + /// A brief description of the flow's purpose. + /// The IFlowContext instance to enable method chaining. + IFlowContext WithDescription(string description); + + /// + /// Saves the current flow and its associated agents to a zip archive at the specified path. + /// This method also includes a text file for the flow description. + /// + /// The file path where the flow and its agents should be saved. + /// The IFlowContext instance to enable method chaining. + IFlowContext Save(string path); + + /// + /// Loads an existing flow from a zip archive located at the specified path. + /// This archive should contain the flow description and agent files in JSON format. + /// + /// The file path where the flow archive is stored. + /// The IFlowContext instance to enable method chaining. + IFlowContext Load(string path); + + /// + /// Adds an agent to the flow. This allows you to dynamically update the flow with new agents. + /// + /// The Agent to be added to the flow. + /// The IFlowContext instance to enable method chaining. + IFlowContext AddAgent(Agent agent); + + /// + /// Adds a collection of agents to the flow. This method enables the batch addition of multiple agents at once. + /// + /// The collection of agents to be added to the flow. + /// The IFlowContext instance to enable method chaining. + IFlowContext AddAgents(IEnumerable agents); + + /// + /// Processes a chat through the first agent in the flow, generating a response based on the chat's messages and the agent's context. + /// + /// The Chat object to process. + /// A flag indicating whether the response should be translated. Default is false. + /// A ChatResult object containing the processed message and other related information. + Task ProcessAsync(Chat chat, bool translate = false); + + /// + /// Processes a user-provided message through the first agent in the flow, generating a response based on the agent's context. + /// + /// The message to be processed by the agent. + /// A flag indicating whether the response should be translated. Default is false. + /// A ChatResult object containing the processed message and other related information. + Task ProcessAsync(string message, bool translate = false); + + /// + /// Processes a message object through the first agent in the flow, generating a response based on the agent's context and message data. + /// + /// The Message object to be processed. + /// A flag indicating whether the response should be translated. Default is false. + /// A ChatResult object containing the processed message and other related information. + Task ProcessAsync(Message message, bool translate = false); + + /// + /// Creates and persists the current flow asynchronously. + /// + /// The created AgentFlow object. + Task CreateAsync(); + + /// + /// Deletes the current flow from the system. + /// + /// A task representing the asynchronous delete operation. + /// Thrown if the flow ID is not set. + Task Delete(); + + /// + /// Retrieves the current flow with its latest state from the system. + /// + /// The current AgentFlow object. + /// Thrown if the flow ID is not set. + Task GetCurrentFlow(); + + /// + /// Retrieves all flows available in the system. + /// + /// A list of all AgentFlow objects. + Task> GetAllFlows(); + + /// + /// Loads an existing flow from the system by its ID and initializes the context with it. + /// + /// The unique identifier of the flow to load. + /// The IFlowContext instance initialized with the existing flow. + /// Thrown if the flow with the specified ID is not found. + Task FromExisting(string flowId); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/McpContext/IMcpContext.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/McpContext/IMcpContext.cs new file mode 100644 index 00000000..1de8f5bd --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/McpContext/IMcpContext.cs @@ -0,0 +1,29 @@ +using MaIN.Domain.Configuration; +using MaIN.Domain.Entities; +using MaIN.Services.Services.Models; + +namespace MaIN.Core.Hub.Contexts.Interfaces.McpContext; + +public interface IMcpContext +{ + /// + /// Sets the MCP configuration for the context. This configuration defines the connection parameters and settings required + /// to interact with MCP servers. + /// + /// The configuration object containing server connection details and settings. + IMcpContext WithConfig(Mcp mcpConfig); + + /// + /// Specifies the backend type to be used for MCP operations. This allows you to select different backend implementations + /// based on your requirements. + /// + /// The enum value specifying which backend implementation to use. + IMcpContext WithBackend(BackendType backendType); + + /// + /// Asynchronously processes a prompt through the configured MCP service, sending the prompt to the MCP server and returning the processed result. + /// + /// The text prompt to be processed by the MCP service + /// A object containing the processed response from the MCP server. + Task PromptAsync(string prompt); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/Interfaces/ModelContext/IModelContext.cs b/src/MaIN.Core/Hub/Contexts/Interfaces/ModelContext/IModelContext.cs new file mode 100644 index 00000000..3ec46647 --- /dev/null +++ b/src/MaIN.Core/Hub/Contexts/Interfaces/ModelContext/IModelContext.cs @@ -0,0 +1,88 @@ +using MaIN.Domain.Models; + +namespace MaIN.Core.Hub.Contexts.Interfaces.ModelContext; + +public interface IModelContext +{ + /// + /// Retrieves a complete list of all available models in the system. This method returns all known models that + /// can be used within the MaIN framework. + /// + /// A list of containing all available models in the system + List GetAll(); + + /// + /// Retrieves information about a specific model by its name. This method allows you to get detailed information about a particular model, + /// including its configuration and metadata. + /// + /// The name of the model to retrieve. + /// A object containing the model's information and configuration. + Model GetModel(string model); + + /// + /// Retrieves the designated embedding model used for generating vector representations of text. + /// + /// A Model object representing the embedding model. + Model GetEmbeddingModel(); + + /// + /// Checks whether a specific model exists locally on the filesystem. + /// + /// The name of the model to check for existence. + /// True if the model file exists locally; otherwise, false. + /// Thrown if the model name is null or empty. + bool Exists(string modelName); + + /// + /// Asynchronously downloads a known model from its configured download URL. + /// + /// The name of the model to download. + /// Optional cancellation token to abort the download operation. + /// A task that represents the asynchronous download operation, returning the IModelContext instance for method chaining. + /// Thrown if the model name is null or empty. + Task DownloadAsync(string modelName, CancellationToken cancellationToken = default); + + /// + /// Asynchronously downloads a custom model from a specified URL. + /// + /// The name to assign to the downloaded model. + /// The URL from which to download the model. + /// A task that represents the asynchronous download operation, returning the IModelContext instance for method chaining. + /// Thrown if the model name or URL is null or empty. + Task DownloadAsync(string model, string url); + + /// + /// Synchronously downloads a known model from its configured download URL. + /// + /// The name of the model to download. + /// The IModelContext instance for method chaining. + /// Thrown if the model name is null or empty. + [Obsolete("Use DownloadAsync instead")] + IModelContext Download(string modelName); + + /// + /// Synchronously downloads a custom model from a specified URL. + /// + /// The name to assign to the downloaded model. + /// The URL from which to download the model. + /// The IModelContext instance for method chaining. + /// Thrown if the model name or URL is null or empty. + [Obsolete("Use DownloadAsync instead")] + IModelContext Download(string model, string url); + + /// + /// Loads a model into the memory cache for faster access during inference operations. + /// + /// The Model object to load into cache. + /// The IModelContext instance for method chaining. + /// Thrown if the model parameter is null. + IModelContext LoadToCache(Model model); + + /// + /// Asynchronously loads a model into the memory cache for faster access during inference operations. + /// + /// The Model object to load into a cache. + /// A task that represents the asynchronous load operation, returning the IModelContext instance for method chaining. + /// Thrown if the model parameter is null. + Task LoadToCacheAsync(Model model); +} \ No newline at end of file diff --git a/src/MaIN.Core/Hub/Contexts/McpContext.cs b/src/MaIN.Core/Hub/Contexts/McpContext.cs index 356ff538..069da167 100644 --- a/src/MaIN.Core/Hub/Contexts/McpContext.cs +++ b/src/MaIN.Core/Hub/Contexts/McpContext.cs @@ -1,3 +1,4 @@ +using MaIN.Core.Hub.Contexts.Interfaces.McpContext; using MaIN.Domain.Configuration; using MaIN.Domain.Entities; using MaIN.Domain.Exceptions.MPC; @@ -7,7 +8,7 @@ namespace MaIN.Core.Hub.Contexts; -public sealed class McpContext +public sealed class McpContext : IMcpContext { private readonly IMcpService _mcpService; private Mcp? _mcpConfig; @@ -18,13 +19,13 @@ internal McpContext(IMcpService mcpService) _mcpConfig = Mcp.NotSet; } - public McpContext WithConfig(Mcp mcpConfig) + public IMcpContext WithConfig(Mcp mcpConfig) { _mcpConfig = mcpConfig; return this; } - public McpContext WithBackend(BackendType backendType) + public IMcpContext WithBackend(BackendType backendType) { _mcpConfig!.Backend = backendType; return this; diff --git a/src/MaIN.Core/Hub/Contexts/ModelContext.cs b/src/MaIN.Core/Hub/Contexts/ModelContext.cs index 944d45af..5bfa66ca 100644 --- a/src/MaIN.Core/Hub/Contexts/ModelContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ModelContext.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Net; +using MaIN.Core.Hub.Contexts.Interfaces.ModelContext; using MaIN.Domain.Configuration; using MaIN.Domain.Exceptions.Models; using MaIN.Domain.Models; @@ -8,7 +9,7 @@ namespace MaIN.Core.Hub.Contexts; -public sealed class ModelContext +public sealed class ModelContext : IModelContext { private readonly MaINSettings _settings; private readonly IHttpClientFactory _httpClientFactory; @@ -16,7 +17,6 @@ public sealed class ModelContext private const int DefaultBufferSize = 8192; private const int FileStreamBufferSize = 65536; private const int ProgressUpdateIntervalMilliseconds = 1000; - private const string MissingModelName = "Model name cannot be null or empty"; private static readonly TimeSpan DefaultHttpTimeout = TimeSpan.FromMinutes(30); internal ModelContext(MaINSettings settings, IHttpClientFactory httpClientFactory) @@ -43,11 +43,11 @@ public bool Exists(string modelName) return File.Exists(modelPath); } - public async Task DownloadAsync(string modelName, CancellationToken cancellationToken = default) + public async Task DownloadAsync(string modelName, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(modelName)) { - throw new ArgumentException(MissingModelName, nameof(modelName)); + throw new MissingModelNameException(nameof(modelName)); } var model = KnownModels.GetModel(modelName); @@ -55,11 +55,11 @@ public async Task DownloadAsync(string modelName, CancellationToke return this; } - public async Task DownloadAsync(string model, string url) + public async Task DownloadAsync(string model, string url) { if (string.IsNullOrWhiteSpace(model)) { - throw new ArgumentException(MissingModelName, nameof(model)); + throw new MissingModelNameException(nameof(model)); } if (string.IsNullOrWhiteSpace(url)) @@ -76,11 +76,11 @@ public async Task DownloadAsync(string model, string url) } [Obsolete("Use async method instead")] - public ModelContext Download(string modelName) + public IModelContext Download(string modelName) { if (string.IsNullOrWhiteSpace(modelName)) { - throw new ArgumentException(MissingModelName, nameof(modelName)); + throw new MissingModelNameException(nameof(modelName)); } var model = KnownModels.GetModel(modelName); @@ -89,11 +89,11 @@ public ModelContext Download(string modelName) } [Obsolete("Obsolete async method instead")] - public ModelContext Download(string model, string url) + public IModelContext Download(string model, string url) { if (string.IsNullOrWhiteSpace(model)) { - throw new ArgumentException(MissingModelName, nameof(model)); + throw new MissingModelNameException(nameof(model)); } if (string.IsNullOrWhiteSpace(url)) @@ -109,7 +109,7 @@ public ModelContext Download(string model, string url) return this; } - public ModelContext LoadToCache(Model model) + public IModelContext LoadToCache(Model model) { ArgumentNullException.ThrowIfNull(model); @@ -118,7 +118,7 @@ public ModelContext LoadToCache(Model model) return this; } - public async Task LoadToCacheAsync(Model model) + public async Task LoadToCacheAsync(Model model) { ArgumentNullException.ThrowIfNull(model); diff --git a/src/MaIN.Domain/Exceptions/Models/MissingModelNameException.cs b/src/MaIN.Domain/Exceptions/Models/MissingModelNameException.cs new file mode 100644 index 00000000..ea5e299b --- /dev/null +++ b/src/MaIN.Domain/Exceptions/Models/MissingModelNameException.cs @@ -0,0 +1,10 @@ +using System.Net; + +namespace MaIN.Domain.Exceptions.Models; + +public class MissingModelNameException(string modelNameParameter) + : MaINCustomException($"Model name cannot be null or empty, {modelNameParameter}.") +{ + public override string PublicErrorMessage => "Model name cannot be null or empty"; + public override HttpStatusCode HttpStatusCode => HttpStatusCode.BadRequest; +} \ No newline at end of file