diff --git a/Controllers/XAiServiceControllerBase.cs b/Controllers/XAiServiceControllerBase.cs index 9eebff1..0b03124 100644 --- a/Controllers/XAiServiceControllerBase.cs +++ b/Controllers/XAiServiceControllerBase.cs @@ -652,6 +652,56 @@ namespace xAiApi.Controllers await Response.Body.FlushAsync(); } } + + /// + /// Ask Question with Files and Get Response ... + /// + /// + /// + /// + [HttpPost("AskWithFiles")] + [Consumes("multipart/form-data")] + public async Task> AskWithFiles( + [FromForm] XAiResponseRequest request, + [FromForm] IFormFileCollection files, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Validate ... + if (!request.IsValid()) + { + XException.InvalidArgs.Throw(); + } + + // + var userInfo = await GetUserInfo(); + var connectionId = GetConnectionId(); + + // + var result = await aiService.AskAsync( + files: files, + userInfo: userInfo, + 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; + } + } #endregion } } \ No newline at end of file diff --git a/Interfaces/IXAiServiceBase.cs b/Interfaces/IXAiServiceBase.cs index 582b6aa..69009ff 100644 --- a/Interfaces/IXAiServiceBase.cs +++ b/Interfaces/IXAiServiceBase.cs @@ -5,6 +5,7 @@ using System.Threading; using xAiModels.Models; using xAiModels.Models.Dtos; using System.Threading.Tasks; +using xIdentityModels.Models; using Microsoft.Extensions.AI; using Microsoft.AspNetCore.Http; using System.Collections.Generic; @@ -87,7 +88,7 @@ namespace xAiApi.Interfaces #endregion // - #region Data Provider Based Actions ... + #region Loaders ... /// /// Load AI Projects ... /// @@ -126,7 +127,10 @@ namespace xAiApi.Interfaces Guid conversationId, CancellationToken cancellationToken = default ); - + #endregion + + // + #region Queries ... /// /// Query Projects ... /// @@ -139,7 +143,7 @@ namespace xAiApi.Interfaces XQuery query, CancellationToken cancellationToken = default ); - + /// /// Query Conversations ... /// @@ -154,7 +158,7 @@ namespace xAiApi.Interfaces XQuery query, CancellationToken cancellationToken = default ); - + /// /// Query Messages ... /// @@ -169,7 +173,10 @@ namespace xAiApi.Interfaces XQuery query, CancellationToken cancellationToken = default ); + #endregion + // + #region Creators ... /// /// Create an AI Project ... /// @@ -201,7 +208,10 @@ namespace xAiApi.Interfaces string connectionId = null, CancellationToken cancellationToken = default ); + #endregion + // + #region Data Actions ... /// /// Ask a Question as Prompt and Retrieve Response ... /// @@ -211,6 +221,7 @@ namespace xAiApi.Interfaces /// /// /// + /// /// /// Task AskAsync( @@ -220,6 +231,7 @@ namespace xAiApi.Interfaces Guid conversationId, string connectionId = null, IFormFileCollection files = null, + XUserClaimsInfoDto userInfo = null, CancellationToken cancellationToken = default ); diff --git a/Interfaces/IXAiServiceControllerBase.cs b/Interfaces/IXAiServiceControllerBase.cs index e073f63..3dc95e4 100644 --- a/Interfaces/IXAiServiceControllerBase.cs +++ b/Interfaces/IXAiServiceControllerBase.cs @@ -6,6 +6,7 @@ using xAiModels.Models.Dtos; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; +using Microsoft.AspNetCore.Http; namespace xAiApi.Interfaces { @@ -176,6 +177,20 @@ namespace xAiApi.Interfaces [FromBody] XAiResponseRequest request, CancellationToken cancellationToken = default ); + + /// + /// Ask Question with Files and Get Response ... + /// + /// + /// + /// + [HttpPost("AskWithFiles")] + [Consumes("multipart/form-data")] + Task> AskWithFiles( + [FromForm] XAiResponseRequest request, + [FromForm] IFormFileCollection files, + CancellationToken cancellationToken = default + ); #endregion } } \ No newline at end of file diff --git a/Providers/XAIServiceBase.cs b/Providers/XAIServiceBase.cs index 30092fe..06eb38b 100644 --- a/Providers/XAIServiceBase.cs +++ b/Providers/XAIServiceBase.cs @@ -15,11 +15,14 @@ using xCommons.Extensions; using xAiModels.Constants; using xAiModels.Interfaces; using xAiModels.Extensions; +using xAiService.Constants; using xAiApi.Configurations; using xExceptions.Constants; using xAiModels.Models.Dtos; using xAiService.Extensions; using System.Threading.Tasks; +using xIdentityModels.Models; +using xFileService.Interfaces; using Microsoft.Extensions.AI; using Microsoft.AspNetCore.Http; using System.Collections.Generic; @@ -44,12 +47,14 @@ namespace xAiApi.Providers /// public XAiModelDescriptor Descriptor { get; } + private readonly IXFileProvider fileProvider; private readonly IXAiDataProvider dataProvider; private readonly ILogger logger; private readonly XAiApiConfiguration configuration; private readonly XValidationProvider validationProvider; protected XAIServiceBase( + IXFileProvider fileProvider, IXAiDataProvider dataProvider, ILogger logger, XAiApiConfiguration configuration, @@ -59,6 +64,7 @@ namespace xAiApi.Providers { // this.logger = logger; + this.fileProvider = fileProvider; this.dataProvider = dataProvider; this.configuration = configuration; this.validationProvider = validationProvider; @@ -347,7 +353,7 @@ namespace xAiApi.Providers #endregion // - #region Data Provider Based Actions ... + #region Loaders ... /// /// Load AI Projects ... /// @@ -422,8 +428,6 @@ namespace xAiApi.Providers return result; } - - /// /// Load Conversations of Specified AI Project ... /// @@ -483,7 +487,153 @@ namespace xAiApi.Providers // return result; } + #endregion + // + #region Queries ... + /// + /// Query Projects ... + /// + /// + /// + /// + /// + public async Task> QueryProjects( + string ownerId, + XQuery query, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (ownerId.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + var result = await dataProvider.QueryProjects( + query: query, + ownerId: ownerId, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Query Conversations ... + /// + /// + /// + /// + /// + /// + public async Task> QueryConversations( + string ownerId, + Guid projectId, + XQuery query, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (ownerId.IsNullOrEmpty() || + projectId.IsDefaultGuid()) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Project ... + var project = await dataProvider.GetProject( + id: projectId, + fillConversations: false, + fillConversationMessages: false, + cancellationToken: cancellationToken + ); + if (project.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Validate Owner ... + if (ownerId != project.OwnerId) + { + XException.NotAllowed.Throw(); + } + + // + var result = await dataProvider.QueryConversations( + query: query, + ownerId: ownerId, + projectId: projectId, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Query Messages ... + /// + /// + /// + /// + /// + /// + public async Task> QueryMessages( + string ownerId, + Guid conversationId, + XQuery query, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (ownerId.IsNullOrEmpty() || + conversationId.IsDefaultGuid()) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Conversation ... + var conversation = await dataProvider.GetConversation( + id: conversationId, + fillMessages: false, + cancellationToken: cancellationToken + ); + if (conversation.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Validate Owner ... + if (ownerId != conversation.OwnerId) + { + XException.NotAllowed.Throw(); + } + + // + var result = await dataProvider.QueryMessages( + query: query, + ownerId: ownerId, + conversationId: conversationId, + cancellationToken: cancellationToken + ); + + // + return result; + } + #endregion + + // + #region Creators ... /// /// Create an AI Project ... /// @@ -540,7 +690,10 @@ namespace xAiApi.Providers // return result; } + #endregion + // + #region Data Actions ... /// /// Ask a Question as Prompt and Retrieve Response ... /// @@ -550,6 +703,7 @@ namespace xAiApi.Providers /// /// /// + /// /// /// public async Task AskAsync( @@ -559,6 +713,7 @@ namespace xAiApi.Providers Guid conversationId, string connectionId = null, IFormFileCollection files = null, + XUserClaimsInfoDto userInfo = null, CancellationToken cancellationToken = default ) { @@ -638,8 +793,26 @@ namespace xAiApi.Providers ); // - // TODO: Parse Files ... - // for Attached them into Prompt Message ... + // Upload Files ... + IEnumerable uploadedFiles = null; + if (files.HasChild()) + { + // + // Validate User Info ... + if (userInfo.IsNullOrDefault()) + { + XException.InvalidArgs.Throw(); + } + + // + // Upload Files ... + uploadedFiles = await fileProvider.Upload( + files: files, + userInfo: userInfo, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + } // // Handle Memory ... @@ -660,21 +833,55 @@ namespace xAiApi.Providers ConversationId = conversationId, ConversationTitle = conversation.Title }; + + // + // Attached Uploaded File Id's as Meta Data ... + if (uploadedFiles.HasChild()) + { + // + var fileIds = uploadedFiles.Select(f => f.Id).ToList(); + promptMessage.MetaDatas = new Dictionary + { + { XAiServiceConstants.XMetaDataFileIdsIdentifier, fileIds } + }.ToJSON(); + } + + // + // Save Prompt Message ... promptMessage = await dataProvider.AddMessage( item: promptMessage, connectionId: connectionId, conversationId: conversationId, cancellationToken: cancellationToken ); - promptMessage = await dataProvider.Enrich( - source: promptMessage, - cancellationToken: cancellationToken - ); // - // Attach Uploaded Files ... + // Since Prompt Message Has Id now, we can Attach Uploaded Files to it ... + IList fileContents = null; + if (uploadedFiles.HasChild()) + { + // + foreach (var uf in uploadedFiles) + { + // + await fileProvider.AddReference( + id: uf.Id, + userInfo: userInfo, + connectionId: connectionId, + forProvidedId: promptMessage.Id, + cancellationToken: cancellationToken, + providedFor: XAiServiceConstants.XAiMessageFilesIdentifier + ); + } + + // + // TODO: Extract File Contents ... + } + + // + // Converts to ChatMessage ... var promptChatMessage = promptMessage - .ToChatMessages(); + .ToChatMessages(fileContents); // // Ask Questions From LLM ... @@ -701,10 +908,6 @@ namespace xAiApi.Providers conversationId: conversationId, cancellationToken: cancellationToken ); - result = await dataProvider.Enrich( - source: result, - cancellationToken: cancellationToken - ); // return result; @@ -904,149 +1107,6 @@ namespace xAiApi.Providers } #endregion - // - #region Query ... - /// - /// Query Projects ... - /// - /// - /// - /// - /// - public async Task> QueryProjects( - string ownerId, - XQuery query, - CancellationToken cancellationToken = default - ) - { - // - // Validate ... - if (ownerId.IsNullOrEmpty()) - { - XException.InvalidArgs.Throw(); - } - - // - var result = await dataProvider.QueryProjects( - query: query, - ownerId: ownerId, - cancellationToken: cancellationToken - ); - - // - return result; - } - - /// - /// Query Conversations ... - /// - /// - /// - /// - /// - /// - public async Task> QueryConversations( - string ownerId, - Guid projectId, - XQuery query, - CancellationToken cancellationToken = default - ) - { - // - // Validate ... - if (ownerId.IsNullOrEmpty() || - projectId.IsDefaultGuid()) - { - XException.InvalidArgs.Throw(); - } - - // - // Retrieve Project ... - var project = await dataProvider.GetProject( - id: projectId, - fillConversations: false, - fillConversationMessages: false, - cancellationToken: cancellationToken - ); - if (project.IsNullOrDefault()) - { - XException.NotFound.Throw(); - } - - // - // Validate Owner ... - if (ownerId != project.OwnerId) - { - XException.NotAllowed.Throw(); - } - - // - var result = await dataProvider.QueryConversations( - query: query, - ownerId: ownerId, - projectId: projectId, - cancellationToken: cancellationToken - ); - - // - return result; - } - - /// - /// Query Messages ... - /// - /// - /// - /// - /// - /// - public async Task> QueryMessages( - string ownerId, - Guid conversationId, - XQuery query, - CancellationToken cancellationToken = default - ) - { - // - // Validate ... - if (ownerId.IsNullOrEmpty() || - conversationId.IsDefaultGuid()) - { - XException.InvalidArgs.Throw(); - } - - // - // Retrieve Conversation ... - var conversation = await dataProvider.GetConversation( - id: conversationId, - fillMessages: false, - cancellationToken: cancellationToken - ); - if (conversation.IsNullOrDefault()) - { - XException.NotFound.Throw(); - } - - // - // Validate Owner ... - if (ownerId != conversation.OwnerId) - { - XException.NotAllowed.Throw(); - } - - // - var result = await dataProvider.QueryMessages( - query: query, - ownerId: ownerId, - conversationId: conversationId, - cancellationToken: cancellationToken - ); - - // - return result; - } - #endregion - /// /// Base Dispose Action ... /// diff --git a/Providers/XAiEmbeddingServiceBase.cs b/Providers/XAiEmbeddingServiceBase.cs index 0a1cda3..cd96a26 100644 --- a/Providers/XAiEmbeddingServiceBase.cs +++ b/Providers/XAiEmbeddingServiceBase.cs @@ -16,6 +16,7 @@ using xAiModels.Interfaces; using xAiApi.Configurations; using xExceptions.Constants; using System.Threading.Tasks; +using xFileService.Interfaces; using Microsoft.Extensions.AI; using System.Collections.Generic; using Microsoft.Extensions.Logging; @@ -28,15 +29,17 @@ namespace xAiApi.Providers /// public abstract class XAiEmbeddingServiceBase : XAIServiceBase, IXAiEmbeddingServiceBase { + private readonly IXFileProvider fileProvider; private readonly XAiModelDescriptor descriptor; private readonly ILogger logger; protected XAiEmbeddingServiceBase( + IXFileProvider fileProvider, IXAiDataProvider dataProvider, XAiApiConfiguration configuration, ILogger logger, string embeddingModelName = XAiApiConstants.XAiEmbeddingModelName - ) : base(dataProvider, logger, configuration, null) + ) : base(fileProvider, dataProvider, logger, configuration, null) { // // Prepare Embedding Model Descriptor ... @@ -46,6 +49,8 @@ namespace xAiApi.Providers XException.InvalidConfiguration.Throw(); } + this.fileProvider = fileProvider; + // this.logger = logger; } diff --git a/Providers/XDefaultAiService.cs b/Providers/XDefaultAiService.cs index 29852d4..7ae7a5e 100644 --- a/Providers/XDefaultAiService.cs +++ b/Providers/XDefaultAiService.cs @@ -3,6 +3,7 @@ using xAiApi.Interfaces; using xCommons.Providers; using xAiModels.Interfaces; using xAiApi.Configurations; +using xFileService.Interfaces; using Microsoft.Extensions.Logging; namespace xAiApi.Providers @@ -10,11 +11,13 @@ namespace xAiApi.Providers public class XDefaultAiService : XAIServiceBase, IXDefaultAiService { public XDefaultAiService( + IXFileProvider fileProvider, IXAiDataProvider dataProvider, ILogger logger, XAiApiConfiguration configuration, XValidationProvider validationProvider ) : base( + fileProvider, dataProvider, logger, configuration, diff --git a/Providers/XDefaultEmbeddingService.cs b/Providers/XDefaultEmbeddingService.cs index ced9f0d..2ca0d70 100644 --- a/Providers/XDefaultEmbeddingService.cs +++ b/Providers/XDefaultEmbeddingService.cs @@ -2,6 +2,7 @@ using xAiApi.Constants; using xAiApi.Interfaces; using xAiModels.Interfaces; using xAiApi.Configurations; +using xFileService.Interfaces; using Microsoft.Extensions.Logging; namespace xAiApi.Providers @@ -9,10 +10,12 @@ namespace xAiApi.Providers public class XDefaultEmbeddingService : XAiEmbeddingServiceBase, IXDefaultEmbeddingService { public XDefaultEmbeddingService( + IXFileProvider fileProvider, IXAiDataProvider dataProvider, XAiApiConfiguration configuration, ILogger logger ) : base( + fileProvider, dataProvider, configuration, logger, diff --git a/Providers/XDefaultThinkingAiService.cs b/Providers/XDefaultThinkingAiService.cs index e42a207..6b83d04 100644 --- a/Providers/XDefaultThinkingAiService.cs +++ b/Providers/XDefaultThinkingAiService.cs @@ -4,6 +4,7 @@ using xCommons.Providers; using xAiModels.Interfaces; using xAiApi.Configurations; using Microsoft.Extensions.AI; +using xFileService.Interfaces; using Microsoft.Extensions.Logging; namespace xAiApi.Providers @@ -11,6 +12,7 @@ namespace xAiApi.Providers public class XDefaultThinkingAiService : XAIServiceBase, IXDefaultThinkingAiService { public XDefaultThinkingAiService( + IXFileProvider fileProvider, IXAiDataProvider dataProvider, ILogger logger, XAiApiConfiguration configuration, @@ -18,6 +20,7 @@ namespace xAiApi.Providers ReasoningEffort effort = ReasoningEffort.Medium, ReasoningOutput output = ReasoningOutput.Full ) : base( + fileProvider, dataProvider, logger, configuration,