diff --git a/Controllers/DefaultAiController.cs b/Controllers/DefaultAiController.cs index 828f2a5..1ca2e82 100644 --- a/Controllers/DefaultAiController.cs +++ b/Controllers/DefaultAiController.cs @@ -1,25 +1,28 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using xAiApi.Interfaces; -using xCommons.Configurations; using xCommons.Providers; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; namespace xAiApi.Controllers { [Authorize] - [Route ("[controller]")] + [Route("[controller]")] public class DefaultAiController : XAiServiceControllerBase { public DefaultAiController( ILogger logger, IXDefaultAiService aiService, XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, XValidationProvider validationProvider ) : base( logger, aiService, appConfiguration, + identityProvider, validationProvider ) { } diff --git a/Controllers/DefaultEmbeddingController.cs b/Controllers/DefaultEmbeddingController.cs index 9754b5b..48d3b5f 100644 --- a/Controllers/DefaultEmbeddingController.cs +++ b/Controllers/DefaultEmbeddingController.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using xAiApi.Interfaces; -using xCommons.Configurations; using xCommons.Providers; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; namespace xAiApi.Controllers { @@ -19,11 +16,13 @@ namespace xAiApi.Controllers ILogger logger, IXDefaultEmbeddingService aiService, XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, XValidationProvider validationProvider ) : base( logger, aiService, appConfiguration, + identityProvider, validationProvider ) { } diff --git a/Controllers/DefaultThinkingAiController.cs b/Controllers/DefaultThinkingAiController.cs index 5da6308..ec101a5 100644 --- a/Controllers/DefaultThinkingAiController.cs +++ b/Controllers/DefaultThinkingAiController.cs @@ -1,7 +1,8 @@ -using Microsoft.Extensions.Logging; using xAiApi.Interfaces; -using xCommons.Configurations; using xCommons.Providers; +using xCommons.Configurations; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; namespace xAiApi.Controllers { @@ -11,8 +12,15 @@ namespace xAiApi.Controllers ILogger logger, IXDefaultThinkingAiService aiService, XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, XValidationProvider validationProvider - ) : base(logger, aiService, appConfiguration, validationProvider) + ) : base( + logger, + aiService, + appConfiguration, + identityProvider, + validationProvider + ) { } } } \ No newline at end of file diff --git a/Controllers/XAiEmbeddingServiceControllerBase.cs b/Controllers/XAiEmbeddingServiceControllerBase.cs index 42d43d3..eebea65 100644 --- a/Controllers/XAiEmbeddingServiceControllerBase.cs +++ b/Controllers/XAiEmbeddingServiceControllerBase.cs @@ -1,13 +1,14 @@ using System; -using System.Collections.Generic; using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using xAiApi.Interfaces; -using xCommons.Configurations; using xCommons.Providers; +using System.Threading.Tasks; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Authorization; namespace xAiApi.Controllers { @@ -22,11 +23,13 @@ namespace xAiApi.Controllers ILogger logger, TAiService aiService, XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, XValidationProvider validationProvider ) : base( logger, aiService, appConfiguration, + identityProvider, validationProvider ) { diff --git a/Controllers/XAiServiceControllerBase.cs b/Controllers/XAiServiceControllerBase.cs index 445ee06..5518da3 100644 --- a/Controllers/XAiServiceControllerBase.cs +++ b/Controllers/XAiServiceControllerBase.cs @@ -1,21 +1,28 @@ using System; using System.Text.Json; using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; +using xAiModels.Models; using xAiApi.Interfaces; -using xCommons.Configurations; -using xCommons.Controllers; using xCommons.Providers; +using xCommons.Extensions; +using xAiModels.Extensions; +using xAiModels.Models.Dtos; +using xExceptions.Constants; +using System.Threading.Tasks; +using xCommons.Configurations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; +using xIdentityService.Controllers; +using Microsoft.AspNetCore.Authorization; namespace xAiApi.Controllers { [Authorize] [Route("[controller]")] - public abstract class XAiServiceControllerBase : XBaseController, IXAiServiceControllerBase + public abstract class XAiServiceControllerBase : XBaseIdentityApiV1Controller, IXAiServiceControllerBase where TAiService : IXAiServiceBase { private readonly TAiService aiService; @@ -24,16 +31,20 @@ namespace xAiApi.Controllers ILogger logger, TAiService aiService, XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, XValidationProvider validationProvider ) : base( logger, appConfiguration, + identityProvider, validationProvider ) { this.aiService = aiService; } + // + #region Actions ... /// /// Ask Question and Get Response ... /// @@ -150,5 +161,175 @@ namespace xAiApi.Controllers await Response.Body.FlushAsync(); } } + #endregion + + // + #region Data Provided Actions ... + /// + /// Load AI Projects ... + /// + /// + /// + [HttpGet("LoadProjects")] + public async Task>> LoadProjects( + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + + // + var userInfo = await GetUserInfo(); + var connectionId = GetConnectionId(); + + // + var result = await aiService.LoadProjects( + ownerId: userInfo.UserId, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Ask Question and Get Response ... + /// + /// + /// + /// + [HttpPost("Ask")] + public async Task> Ask( + [FromBody] XAiResponseRequest request, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (!request.IsValid()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + var connectionId = GetConnectionId(); + + // + // TODO: Reading Fiels Form Collection and Attach it ... + var result = await aiService.AskAsync( + files: null, + prompt: request.Prompt, + ownerId: userInfo.UserId, + connectionId: connectionId, + projectId: request.ProjectId, + cancellationToken: cancellationToken, + conversationId: request.ConversationId + ); + + // + return Ok(result); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Ask Question and Get Response Stream ... + /// + /// + /// + /// + [HttpPost("AskStream")] + public async Task AskStream( + [FromBody] XAiResponseRequest request, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (!request.IsValid()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + var connectionId = GetConnectionId(); + + // + // Configure SSE Response Headers ... + Response.Headers["X-Accel-Buffering"] = "no"; + Response.Headers["Connection"] = "keep-alive"; + Response.Headers["Content-Type"] = "text/event-stream"; + Response.Headers["Cache-Control"] = "no-cache, no-store"; + + // + // Disable response buffering ... + var responseBodyFeature = HttpContext.Features + .Get(); + responseBodyFeature?.DisableBuffering(); + + // + // TODO: Reading Fiels Form Collection and Attach it ... + var enumerable = aiService.AskAsEnumerable( + files: null, + prompt: request.Prompt, + ownerId: userInfo.UserId, + connectionId: connectionId, + projectId: request.ProjectId, + cancellationToken: cancellationToken, + conversationId: request.ConversationId + ); + + // + await foreach (var resp in enumerable) + { + // + // Skip Empty Responses ... + if (resp.IsNullOrDefault()) + { + continue; + } + + // + var payload = resp.ToJSON(camelCase: true); + + // + // Write SSE Event ... + await Response.WriteAsync($"{payload}", cancellationToken); + await Response.Body.FlushAsync(cancellationToken); + } + + // + await Response.Body.FlushAsync(cancellationToken); + } + catch (Exception) + { + await Response.Body.FlushAsync(); + } + } + #endregion } } \ No newline at end of file diff --git a/Interfaces/IXAiServiceBase.cs b/Interfaces/IXAiServiceBase.cs index 578df25..c590aa7 100644 --- a/Interfaces/IXAiServiceBase.cs +++ b/Interfaces/IXAiServiceBase.cs @@ -2,8 +2,10 @@ using System; using System.Net.Http; using System.Threading; using xAiModels.Models; +using xAiModels.Models.Dtos; using System.Threading.Tasks; using Microsoft.Extensions.AI; +using Microsoft.AspNetCore.Http; using System.Collections.Generic; namespace xAiApi.Interfaces @@ -23,6 +25,8 @@ namespace xAiApi.Interfaces /// XAiModelDescriptor Descriptor { get; } + // + #region Actions ... /// /// Base Method for Communicate with LLM ... /// @@ -79,5 +83,64 @@ namespace xAiApi.Interfaces ChatMessage message = null, bool forceMessage = true ); + #endregion + + // + #region Data Provider Based Actions ... + /// + /// Load AI Projects ... + /// + /// + /// + /// + /// + Task> LoadProjects( + string ownerId, + string connectionId = null, + CancellationToken cancellationToken = default + ); + + /// + /// Ask a Question as Prompt and Retrieve Response ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task AskAsync( + string prompt, + string ownerId, + Guid projectId, + Guid conversationId, + string connectionId = null, + IFormFileCollection files = null, + CancellationToken cancellationToken = default + ); + + /// + /// Ask a Question as Prompt and Retrieve Response as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + IAsyncEnumerable AskAsEnumerable( + string prompt, + string ownerId, + Guid projectId, + Guid conversationId, + string connectionId = null, + IFormFileCollection files = null, + CancellationToken cancellationToken = default + ); + #endregion } } \ No newline at end of file diff --git a/Interfaces/IXAiServiceControllerBase.cs b/Interfaces/IXAiServiceControllerBase.cs index 8e4aa52..848889e 100644 --- a/Interfaces/IXAiServiceControllerBase.cs +++ b/Interfaces/IXAiServiceControllerBase.cs @@ -1,11 +1,16 @@ using System.Threading; +using xAiModels.Models; +using xAiModels.Models.Dtos; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; namespace xAiApi.Interfaces { public interface IXAiServiceControllerBase { + // + #region Actions ... /// /// Ask Question and Get Response ... /// @@ -28,6 +33,44 @@ namespace xAiApi.Interfaces Task AskTextStream( [FromQuery] string question, CancellationToken cancellationToken = default - ); + ); + #endregion + + // + #region Data Provided Actions ... + /// + /// Load AI Projects ... + /// + /// + /// + [HttpGet("LoadProjects")] + Task>> LoadProjects( + CancellationToken cancellationToken = default + ); + + /// + /// Ask Question and Get Response ... + /// + /// + /// + /// + [HttpPost("Ask")] + Task> Ask( + [FromBody] XAiResponseRequest request, + CancellationToken cancellationToken = default + ); + + /// + /// Ask Question and Get Response Stream ... + /// + /// + /// + /// + [HttpPost("AskStream")] + Task AskStream( + [FromBody] XAiResponseRequest request, + CancellationToken cancellationToken = default + ); + #endregion } } \ No newline at end of file diff --git a/Providers/XAIServiceBase.cs b/Providers/XAIServiceBase.cs index cb2aaa3..300a1b6 100644 --- a/Providers/XAIServiceBase.cs +++ b/Providers/XAIServiceBase.cs @@ -1,6 +1,7 @@ using System; using OpenAI; using OllamaSharp; +using System.Linq; using System.Net.Http; using System.Threading; using xAiApi.Constants; @@ -8,20 +9,22 @@ using xAiModels.Models; using xAiApi.Interfaces; using xAiApi.Extensions; using System.ClientModel; +using xCommons.Providers; using xCommons.Extensions; using xAiModels.Constants; using xAiModels.Interfaces; using xAiModels.Extensions; using xAiApi.Configurations; using xExceptions.Constants; +using xAiModels.Models.Dtos; +using xAiService.Extensions; using System.Threading.Tasks; using Microsoft.Extensions.AI; +using Microsoft.AspNetCore.Http; using System.Collections.Generic; using Microsoft.Extensions.Logging; using System.ClientModel.Primitives; using System.Runtime.CompilerServices; -using xAiModels.Models.Dtos; -using xAiService.Extensions; namespace xAiApi.Providers { @@ -40,17 +43,21 @@ namespace xAiApi.Providers private readonly IXAiDataProvider dataProvider; private readonly ILogger logger; private readonly XAiApiConfiguration configuration; + private readonly XValidationProvider validationProvider; protected XAIServiceBase( IXAiDataProvider dataProvider, ILogger logger, XAiApiConfiguration configuration, + XValidationProvider validationProvider, string model = null ) { + // this.logger = logger; this.dataProvider = dataProvider; this.configuration = configuration; + this.validationProvider = validationProvider; // // Prepare Embedding Model Descriptor ... @@ -337,8 +344,16 @@ namespace xAiApi.Providers // #region Data Provider Based Actions ... + /// + /// Load AI Projects ... + /// + /// + /// + /// + /// public async Task> LoadProjects( string ownerId, + string connectionId = null, CancellationToken cancellationToken = default ) { @@ -352,28 +367,408 @@ namespace xAiApi.Providers // var result = await dataProvider.LoadProjects( ownerId: ownerId, + fillConversations: false, + connectionId: connectionId, + fillConversationMessages: false, cancellationToken: cancellationToken ); // // Here we have to fill Conversations of Default Project ... var defaultProjectId = result.GetDafualtProject(ownerId); - + var defaultProject = result.FirstOrDefault(x => x.Id == defaultProjectId); + result = result.Where(x => x.Id != defaultProjectId); + // // Reading Conversations of Specified Default Project ... - var conversations = await dataProvider.LoadConversations( + var defaultProjectConversations = await dataProvider.LoadConversations( fillMessages: false, + connectionId: connectionId, projectId: defaultProjectId, cancellationToken: cancellationToken ); - var defaultConversationId = conversations.GetDefaultConversation( + var defaultConversationId = defaultProjectConversations.GetDefaultConversation( ownerId: ownerId, projectId: defaultProjectId ); + var defaultConversation = defaultProjectConversations.First(x => x.Id == defaultConversationId); + defaultProjectConversations = defaultProjectConversations.Where(x => x.Id != defaultConversationId); + + // + // Reading Default Conversations Messages ... + var defaultConversationMessages = await dataProvider.LoadMessages( + cancellationToken: cancellationToken, + conversationId: defaultConversationId + ); + + // + defaultConversation.Messages = [.. defaultConversationMessages]; + defaultProjectConversations = [defaultConversation, .. defaultProjectConversations]; + result = [defaultProject, .. result]; + + // + return result; + } + + /// + /// Ask a Question as Prompt and Retrieve Response ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task AskAsync( + string prompt, + string ownerId, + Guid projectId, + Guid conversationId, + string connectionId = null, + IFormFileCollection files = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + validationProvider + .GroupValidationBuilder() + .AddNotEmpty(prompt) + .AddNotEmpty(ownerId) + .ValidateGroup(); + + // + // Check Project ... + XAiProjectDto project = null; + if (projectId.IsDefaultGuid()) + { + // + project = await dataProvider.GetDefaultProject( + ownerId: ownerId, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + projectId = project.Id; + } + else + { + // + project = await dataProvider.GetProject( + id: projectId, + fillConversations: false, + fillConversationMessages: false, + cancellationToken: cancellationToken + ); + } + if (project.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Check Conversation ... + XAiConversationDto conversation = null; + if (conversationId.IsDefaultGuid()) + { + // + conversation = await dataProvider.GetDefaultConversation( + ownerId: ownerId, + projectId: projectId, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + conversationId = conversation.Id; + } + else + { + // + conversation = await dataProvider.GetConversation( + id: conversationId, + fillMessages: false, + cancellationToken: cancellationToken + ); + } + if (conversation.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Reading Messages ... + var messages = await dataProvider.LoadMessages( + conversationId: conversationId, + cancellationToken: cancellationToken + ); + + // + // TODO: Parse Files ... + // for Attached them into Prompt Message ... + + // + // Handle Memory ... + IList history = await PrepareMemory( + project: project, + messages: messages, + cancellationToken: cancellationToken + ); + + // + // Prepare and Add Prompt Message ... + var promptMessage = new XAiMessageDto + { + Content = prompt, + OwnerId = ownerId, + Role = XAiChatRole.User, + CreatedOn = DateTime.UtcNow, + ConversationId = conversationId, + }; + promptMessage = await dataProvider.AddMessage( + item: promptMessage, + connectionId: connectionId, + conversationId: conversationId, + cancellationToken: cancellationToken + ); + promptMessage = await dataProvider.Enrich( + source: promptMessage, + cancellationToken: cancellationToken + ); + + // + // Attach Uploaded Files ... + var promptChatMessage = promptMessage + .ToChatMessages(); + + // + // Ask Questions From LLM ... + var answer = await AskLLMAsync( + history: history, + prompt: promptChatMessage, + cancellationToken: cancellationToken + ); + + // + // Prepare Answer AI Message ... + var result = new XAiMessageDto + { + Content = answer, + OwnerId = ownerId, + Role = XAiChatRole.System, + CreatedOn = DateTime.UtcNow, + ConversationId = conversationId + }; + result = await dataProvider.AddMessage( + item: result, + connectionId: connectionId, + conversationId: conversationId, + cancellationToken: cancellationToken + ); + result = await dataProvider.Enrich( + source: result, + cancellationToken: cancellationToken + ); // return result; } + + /// + /// Ask a Question as Prompt and Retrieve Response as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async IAsyncEnumerable AskAsEnumerable( + string prompt, + string ownerId, + Guid projectId, + Guid conversationId, + string connectionId = null, + IFormFileCollection files = null, + [EnumeratorCancellation] + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + validationProvider + .GroupValidationBuilder() + .AddNotEmpty(prompt) + .AddNotEmpty(ownerId) + .ValidateGroup(); + + // + // Check Project ... + XAiProjectDto project = null; + if (projectId.IsDefaultGuid()) + { + // + project = await dataProvider.GetDefaultProject( + ownerId: ownerId, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + projectId = project.Id; + } + else + { + // + project = await dataProvider.GetProject( + id: projectId, + fillConversations: false, + fillConversationMessages: false, + cancellationToken: cancellationToken + ); + } + if (project.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Check Conversation ... + XAiConversationDto conversation = null; + if (conversationId.IsDefaultGuid()) + { + // + conversation = await dataProvider.GetDefaultConversation( + ownerId: ownerId, + projectId: projectId, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + conversationId = conversation.Id; + } + else + { + // + conversation = await dataProvider.GetConversation( + id: conversationId, + fillMessages: false, + cancellationToken: cancellationToken + ); + } + if (conversation.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Reading Messages ... + var messages = await dataProvider.LoadMessages( + conversationId: conversationId, + cancellationToken: cancellationToken + ); + + // + // TODO: Parse Files ... + // for Attached them into Prompt Message ... + + // + // Handle Memory ... + IList history = await PrepareMemory( + project: project, + messages: messages, + cancellationToken: cancellationToken + ); + + // + // Prepare and Add Prompt Message ... + var promptMessage = new XAiMessageDto + { + Content = prompt, + OwnerId = ownerId, + Role = XAiChatRole.User, + CreatedOn = DateTime.UtcNow, + ConversationId = conversationId, + }; + promptMessage = await dataProvider.AddMessage( + item: promptMessage, + connectionId: connectionId, + conversationId: conversationId, + cancellationToken: cancellationToken + ); + promptMessage = await dataProvider.Enrich( + source: promptMessage, + cancellationToken: cancellationToken + ); + + // + // Attach Uploaded Files ... + var promptChatMessage = promptMessage + .ToChatMessages(); + history.Add(promptChatMessage); + + // + // Ask Questions From LLM ... + using var client = GetClient(); + var enumerable = client.GetStreamingResponseAsync( + options: null, + messages: history + ); + + // + long sequence = 1; + var answer = string.Empty; + await foreach (var res in enumerable) + { + // + // Cancellation Token ... + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + // + var resp = new XAiResonseMessage + { + OwnerId = ownerId, + Content = res.Text, + Sequence = sequence, + Role = XAiChatRole.System, + RespondTo = promptMessage.Id, + RespondedOn = DateTime.UtcNow, + }; + + // + sequence++; + answer += res.Text; + + // + yield return resp; + } + + // + // Prepare Answer AI Message ... + var result = new XAiMessageDto + { + Content = answer, + OwnerId = ownerId, + Role = XAiChatRole.System, + CreatedOn = DateTime.UtcNow, + ConversationId = conversationId + }; + result = await dataProvider.AddMessage( + item: result, + connectionId: connectionId, + conversationId: conversationId, + cancellationToken: cancellationToken + ); + } #endregion /// @@ -382,5 +777,118 @@ namespace xAiApi.Providers /// public virtual void Dispose() { } + + // + #region Private ... + /// + /// Implementing Memory Strategy here ... + /// + /// + /// + /// + /// + private async Task> PrepareMemory( + XAiProjectDto project, + IEnumerable messages, + CancellationToken cancellationToken = default + ) + { + // + var result = new List(); + + // + if (messages.HasChild()) + { + // + // if Messages Has Child, this means + // Default Messages Handled before ... + result = [.. messages.Select(m => m.ToChatMessages())]; + } + else + { + // + // If Message Has not any Childs, this means + // First Asking Questions ... + + // + // Retrieve Introduction Prompt ... + var introductionPrompt = configuration.GetPrompt(XAiApiConstants.XAiApiIntroductionPromptName); + if (introductionPrompt.IsNullOrEmpty()) + { + XException.InvalidData.Throw(); + } + + // + var introductionMessage = new ChatMessage( + role: ChatRole.System, + content: introductionPrompt + ); + + // + result.Add(introductionMessage); + + // + if (!project.IsNullOrDefault() && + !project.Prompt.IsNullOrEmpty()) + { + // + // TODO: Handle Project Files Here ... + var projectMessage = new ChatMessage( + role: ChatRole.System, + contents: [new TextContent(project.Prompt)] + ); + result.Add(projectMessage); + } + } + + // + return result; + } + + /// + /// Ask Quetion from LLM ... + /// + /// + /// + /// + /// + private async Task AskLLMAsync( + ChatMessage prompt, + IList history, + CancellationToken cancellationToken = default + ) + { + // + if (prompt.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + history.Add(prompt); + using var client = GetClient(); + var response = await client.GetResponseAsync( + messages: history, + cancellationToken: cancellationToken + ); + + // + // Validate Response ... + if (!response.IsValid()) + { + // + // Dispose Client ... + client.Dispose(); + XException.ActionFailed.Throw(); + } + + // + // Retrieve Response Text ... + var result = response.Text; + + // + return result; + } + #endregion } } \ No newline at end of file diff --git a/Providers/XDefaultAiService.cs b/Providers/XDefaultAiService.cs index aa74eab..29852d4 100644 --- a/Providers/XDefaultAiService.cs +++ b/Providers/XDefaultAiService.cs @@ -1,5 +1,6 @@ using xAiApi.Constants; using xAiApi.Interfaces; +using xCommons.Providers; using xAiModels.Interfaces; using xAiApi.Configurations; using Microsoft.Extensions.Logging; @@ -11,11 +12,13 @@ namespace xAiApi.Providers public XDefaultAiService( IXAiDataProvider dataProvider, ILogger logger, - XAiApiConfiguration configuration + XAiApiConfiguration configuration, + XValidationProvider validationProvider ) : base( dataProvider, logger, configuration, + validationProvider, XAiApiConstants.XAiDefaultModelName ) { } diff --git a/Providers/XDefaultThinkingAiService.cs b/Providers/XDefaultThinkingAiService.cs index 870940a..e42a207 100644 --- a/Providers/XDefaultThinkingAiService.cs +++ b/Providers/XDefaultThinkingAiService.cs @@ -1,10 +1,10 @@ using xAiApi.Constants; using xAiApi.Interfaces; +using xCommons.Providers; using xAiModels.Interfaces; using xAiApi.Configurations; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.AI; -using xCommons.Extensions; +using Microsoft.Extensions.Logging; namespace xAiApi.Providers { @@ -14,12 +14,14 @@ namespace xAiApi.Providers IXAiDataProvider dataProvider, ILogger logger, XAiApiConfiguration configuration, + XValidationProvider validationProvider, ReasoningEffort effort = ReasoningEffort.Medium, ReasoningOutput output = ReasoningOutput.Full ) : base( dataProvider, logger, configuration, + validationProvider, XAiApiConstants.XAiDefaultThinkingModelName ) {